commit 07cdfb2e4ec761021477271776b81f33af0e731d parent 42a4d666f42ce94274995bfdae644444ff5f6d53 Author: Florian Dold <florian@dold.me> Date: Wed, 17 Mar 2021 17:56:37 +0100 towards wallet-core / util split Diffstat:
116 files changed, 8284 insertions(+), 8375 deletions(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json @@ -42,5 +42,9 @@ "src/i18n/*.po": true }, "search.collapseResults": "auto", - "files.associations": { "api-extractor.json": "jsonc" } + "files.associations": { + "api-extractor.json": "jsonc" + }, + "typescript.preferences.importModuleSpecifierEnding": "js", + "typescript.preferences.importModuleSpecifier": "project-relative" } \ No newline at end of file diff --git a/package.json b/package.json @@ -1,6 +1,7 @@ { "private": true, "scripts": { + "preinstall": "npx only-allow pnpm", "compile": "pnpm run --filter '{packages}' compile", "clean": "pnpm run --filter '{packages}' clean", "pretty": "pnpm run --filter '{packages}' pretty", diff --git a/packages/taler-util/README.md b/packages/taler-util/README.md @@ -0,0 +1,9 @@ +# @gnu-taler/taler-util + +This package implements various utility functionality for GNU Taler. + + +## When should something be moved to this package? + +The ``@gnu-taler/taler-util`` package should have minimal dependencies +and as few platform-specific functionality as possible. diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json @@ -0,0 +1,44 @@ +{ + "name": "@gnu-taler/taler-util", + "version": "0.8.2", + "description": "Generic helper functionality for GNU Taler", + "exports": { + ".": "./lib/index.js" + }, + "module": "./lib/index.js", + "types": "./lib/index.d.ts", + "typesVersions": { + "*": { + "lib/index.d.ts": [ + "lib/index.d.ts" + ], + "src/*": [], + "*": [] + } + }, + "author": "Florian Dold", + "license": "AGPL-3.0-or-later", + "private": false, + "scripts": { + "prepare": "tsc", + "test": "tsc && ava", + "clean": "rimraf dist lib tsconfig.tsbuildinfo", + "pretty": "prettier --write src" + }, + "devDependencies": { + "@types/node": "^14.14.22", + "ava": "^3.15.0", + "esbuild": "^0.9.2", + "prettier": "^2.2.1", + "rimraf": "^3.0.2", + "typescript": "^4.2.3" + }, + "dependencies": { + "tslib": "^2.1.0" + }, + "ava": { + "require": [ + "esm" + ] + } +} diff --git a/packages/taler-util/src/ReserveStatus.ts b/packages/taler-util/src/ReserveStatus.ts @@ -0,0 +1,57 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * @author Florian Dold <dold@taler.net> + */ + +/** + * Imports. + */ +import { + codecForString, + buildCodecForObject, + codecForList, + Codec, +} from "./codec.js"; +import { AmountString } from "./talerTypes"; +import { + ReserveTransaction, + codecForReserveTransaction, +} from "./ReserveTransaction"; + +/** + * Status of a reserve. + * + * Schema type for the exchange's response to "/reserve/status". + */ +export interface ReserveStatus { + /** + * Balance left in the reserve. + */ + balance: AmountString; + + /** + * Transaction history for the reserve. + */ + history: ReserveTransaction[]; +} + +export const codecForReserveStatus = (): Codec<ReserveStatus> => + buildCodecForObject<ReserveStatus>() + .property("balance", codecForString()) + .property("history", codecForList(codecForReserveTransaction())) + .build("ReserveStatus"); diff --git a/packages/taler-util/src/ReserveTransaction.ts b/packages/taler-util/src/ReserveTransaction.ts @@ -0,0 +1,253 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Type declarations for the exchange's reserve transaction information. + * + * @author Florian Dold <dold@taler.net> + */ + +/** + * Imports. + */ +import { + codecForString, + buildCodecForObject, + codecForConstString, + buildCodecForUnion, + Codec, + codecForNumber, +} from "./codec.js"; +import { + AmountString, + Base32String, + EddsaSignatureString, + EddsaPublicKeyString, + CoinPublicKeyString, +} from "./talerTypes"; +import { Timestamp, codecForTimestamp } from "./time.js"; + +export enum ReserveTransactionType { + Withdraw = "WITHDRAW", + Credit = "CREDIT", + Recoup = "RECOUP", + Closing = "CLOSING", +} + +export interface ReserveWithdrawTransaction { + type: ReserveTransactionType.Withdraw; + + /** + * Amount withdrawn. + */ + amount: AmountString; + + /** + * Hash of the denomination public key of the coin. + */ + h_denom_pub: Base32String; + + /** + * Hash of the blinded coin to be signed + */ + h_coin_envelope: Base32String; + + /** + * Signature of 'TALER_WithdrawRequestPS' created with the reserves's + * private key. + */ + reserve_sig: EddsaSignatureString; + + /** + * Fee that is charged for withdraw. + */ + withdraw_fee: AmountString; +} + +export interface ReserveCreditTransaction { + type: ReserveTransactionType.Credit; + + /** + * Amount withdrawn. + */ + amount: AmountString; + + /** + * Sender account payto://-URL + */ + sender_account_url: string; + + /** + * Transfer details uniquely identifying the transfer. + */ + wire_reference: number; + + /** + * Timestamp of the incoming wire transfer. + */ + timestamp: Timestamp; +} + +export interface ReserveClosingTransaction { + type: ReserveTransactionType.Closing; + + /** + * Closing balance. + */ + amount: AmountString; + + /** + * Closing fee charged by the exchange. + */ + closing_fee: AmountString; + + /** + * Wire transfer subject. + */ + wtid: string; + + /** + * Hash of the wire account into which the funds were returned to. + */ + h_wire: string; + + /** + * This is a signature over a + * struct TALER_ReserveCloseConfirmationPS with purpose + * TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED. + */ + exchange_sig: EddsaSignatureString; + + /** + * Public key used to create exchange_sig. + */ + exchange_pub: EddsaPublicKeyString; + + /** + * Time when the reserve was closed. + */ + timestamp: Timestamp; +} + +export interface ReserveRecoupTransaction { + type: ReserveTransactionType.Recoup; + + /** + * Amount paid back. + */ + amount: AmountString; + + /** + * This is a signature over + * a struct TALER_PaybackConfirmationPS with purpose + * TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK. + */ + exchange_sig: EddsaSignatureString; + + /** + * Public key used to create exchange_sig. + */ + exchange_pub: EddsaPublicKeyString; + + /** + * Time when the funds were paid back into the reserve. + */ + timestamp: Timestamp; + + /** + * Public key of the coin that was paid back. + */ + coin_pub: CoinPublicKeyString; +} + +/** + * Format of the exchange's transaction history for a reserve. + */ +export type ReserveTransaction = + | ReserveWithdrawTransaction + | ReserveCreditTransaction + | ReserveClosingTransaction + | ReserveRecoupTransaction; + +export const codecForReserveWithdrawTransaction = (): Codec< + ReserveWithdrawTransaction +> => + buildCodecForObject<ReserveWithdrawTransaction>() + .property("amount", codecForString()) + .property("h_coin_envelope", codecForString()) + .property("h_denom_pub", codecForString()) + .property("reserve_sig", codecForString()) + .property("type", codecForConstString(ReserveTransactionType.Withdraw)) + .property("withdraw_fee", codecForString()) + .build("ReserveWithdrawTransaction"); + +export const codecForReserveCreditTransaction = (): Codec< + ReserveCreditTransaction +> => + buildCodecForObject<ReserveCreditTransaction>() + .property("amount", codecForString()) + .property("sender_account_url", codecForString()) + .property("timestamp", codecForTimestamp) + .property("wire_reference", codecForNumber()) + .property("type", codecForConstString(ReserveTransactionType.Credit)) + .build("ReserveCreditTransaction"); + +export const codecForReserveClosingTransaction = (): Codec< + ReserveClosingTransaction +> => + buildCodecForObject<ReserveClosingTransaction>() + .property("amount", codecForString()) + .property("closing_fee", codecForString()) + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("h_wire", codecForString()) + .property("timestamp", codecForTimestamp) + .property("type", codecForConstString(ReserveTransactionType.Closing)) + .property("wtid", codecForString()) + .build("ReserveClosingTransaction"); + +export const codecForReserveRecoupTransaction = (): Codec< + ReserveRecoupTransaction +> => + buildCodecForObject<ReserveRecoupTransaction>() + .property("amount", codecForString()) + .property("coin_pub", codecForString()) + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("timestamp", codecForTimestamp) + .property("type", codecForConstString(ReserveTransactionType.Recoup)) + .build("ReserveRecoupTransaction"); + +export const codecForReserveTransaction = (): Codec<ReserveTransaction> => + buildCodecForUnion<ReserveTransaction>() + .discriminateOn("type") + .alternative( + ReserveTransactionType.Withdraw, + codecForReserveWithdrawTransaction(), + ) + .alternative( + ReserveTransactionType.Closing, + codecForReserveClosingTransaction(), + ) + .alternative( + ReserveTransactionType.Recoup, + codecForReserveRecoupTransaction(), + ) + .alternative( + ReserveTransactionType.Credit, + codecForReserveCreditTransaction(), + ) + .build<ReserveTransaction>("ReserveTransaction"); diff --git a/packages/taler-util/src/amounts.test.ts b/packages/taler-util/src/amounts.test.ts @@ -0,0 +1,140 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import test from "ava"; + +import { Amounts, AmountJson } from "./amounts.js"; + +const jAmt = ( + value: number, + fraction: number, + currency: string, +): AmountJson => ({ value, fraction, currency }); + +const sAmt = (s: string): AmountJson => Amounts.parseOrThrow(s); + +test("amount addition (simple)", (t) => { + const a1 = jAmt(1, 0, "EUR"); + const a2 = jAmt(1, 0, "EUR"); + const a3 = jAmt(2, 0, "EUR"); + t.true(0 === Amounts.cmp(Amounts.add(a1, a2).amount, a3)); + t.pass(); +}); + +test("amount addition (saturation)", (t) => { + const a1 = jAmt(1, 0, "EUR"); + const res = Amounts.add(jAmt(Amounts.maxAmountValue, 0, "EUR"), a1); + t.true(res.saturated); + t.pass(); +}); + +test("amount subtraction (simple)", (t) => { + const a1 = jAmt(2, 5, "EUR"); + const a2 = jAmt(1, 0, "EUR"); + const a3 = jAmt(1, 5, "EUR"); + t.true(0 === Amounts.cmp(Amounts.sub(a1, a2).amount, a3)); + t.pass(); +}); + +test("amount subtraction (saturation)", (t) => { + const a1 = jAmt(0, 0, "EUR"); + const a2 = jAmt(1, 0, "EUR"); + let res = Amounts.sub(a1, a2); + t.true(res.saturated); + res = Amounts.sub(a1, a1); + t.true(!res.saturated); + t.pass(); +}); + +test("amount comparison", (t) => { + t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(1, 0, "EUR")), 0); + t.is(Amounts.cmp(jAmt(1, 1, "EUR"), jAmt(1, 0, "EUR")), 1); + t.is(Amounts.cmp(jAmt(1, 1, "EUR"), jAmt(1, 2, "EUR")), -1); + t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(0, 0, "EUR")), 1); + t.is(Amounts.cmp(jAmt(0, 0, "EUR"), jAmt(1, 0, "EUR")), -1); + t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(0, 100000000, "EUR")), 0); + t.throws(() => Amounts.cmp(jAmt(1, 0, "FOO"), jAmt(1, 0, "BAR"))); + t.pass(); +}); + +test("amount parsing", (t) => { + t.is( + Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:0"), jAmt(0, 0, "TESTKUDOS")), + 0, + ); + t.is( + Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:10"), jAmt(10, 0, "TESTKUDOS")), + 0, + ); + t.is( + Amounts.cmp( + Amounts.parseOrThrow("TESTKUDOS:0.1"), + jAmt(0, 10000000, "TESTKUDOS"), + ), + 0, + ); + t.is( + Amounts.cmp( + Amounts.parseOrThrow("TESTKUDOS:0.00000001"), + jAmt(0, 1, "TESTKUDOS"), + ), + 0, + ); + t.is( + Amounts.cmp( + Amounts.parseOrThrow("TESTKUDOS:4503599627370496.99999999"), + jAmt(4503599627370496, 99999999, "TESTKUDOS"), + ), + 0, + ); + t.throws(() => Amounts.parseOrThrow("foo:")); + t.throws(() => Amounts.parseOrThrow("1.0")); + t.throws(() => Amounts.parseOrThrow("42")); + t.throws(() => Amounts.parseOrThrow(":1.0")); + t.throws(() => Amounts.parseOrThrow(":42")); + t.throws(() => Amounts.parseOrThrow("EUR:.42")); + t.throws(() => Amounts.parseOrThrow("EUR:42.")); + t.throws(() => Amounts.parseOrThrow("TESTKUDOS:4503599627370497.99999999")); + t.is( + Amounts.cmp( + Amounts.parseOrThrow("TESTKUDOS:0.99999999"), + jAmt(0, 99999999, "TESTKUDOS"), + ), + 0, + ); + t.throws(() => Amounts.parseOrThrow("TESTKUDOS:0.999999991")); + t.pass(); +}); + +test("amount stringification", (t) => { + t.is(Amounts.stringify(jAmt(0, 0, "TESTKUDOS")), "TESTKUDOS:0"); + t.is(Amounts.stringify(jAmt(4, 94000000, "TESTKUDOS")), "TESTKUDOS:4.94"); + t.is(Amounts.stringify(jAmt(0, 10000000, "TESTKUDOS")), "TESTKUDOS:0.1"); + t.is(Amounts.stringify(jAmt(0, 1, "TESTKUDOS")), "TESTKUDOS:0.00000001"); + t.is(Amounts.stringify(jAmt(5, 0, "TESTKUDOS")), "TESTKUDOS:5"); + // denormalized + t.is(Amounts.stringify(jAmt(1, 100000000, "TESTKUDOS")), "TESTKUDOS:2"); + t.pass(); +}); + +test("amount multiplication", (t) => { + t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 0).amount), "EUR:0"); + t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 1).amount), "EUR:1.11"); + t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 2).amount), "EUR:2.22"); + t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 3).amount), "EUR:3.33"); + t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 4).amount), "EUR:4.44"); + t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 5).amount), "EUR:5.55"); +}); diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts @@ -0,0 +1,423 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Types and helper functions for dealing with Taler amounts. + */ + +/** + * Imports. + */ +import { + buildCodecForObject, + codecForString, + codecForNumber, + Codec, +} from "./codec"; +import { AmountString } from "./talerTypes"; + +/** + * Number of fractional units that one value unit represents. + */ +export const fractionalBase = 1e8; + +/** + * How many digits behind the comma are required to represent the + * fractional value in human readable decimal format? Must match + * lg(fractionalBase) + */ +export const fractionalLength = 8; + +/** + * Maximum allowed value field of an amount. + */ +export const maxAmountValue = 2 ** 52; + +/** + * Non-negative financial amount. Fractional values are expressed as multiples + * of 1e-8. + */ +export interface AmountJson { + /** + * Value, must be an integer. + */ + readonly value: number; + + /** + * Fraction, must be an integer. Represent 1/1e8 of a unit. + */ + readonly fraction: number; + + /** + * Currency of the amount. + */ + readonly currency: string; +} + +export const codecForAmountJson = (): Codec<AmountJson> => + buildCodecForObject<AmountJson>() + .property("currency", codecForString()) + .property("value", codecForNumber()) + .property("fraction", codecForNumber()) + .build("AmountJson"); + +export const codecForAmountString = (): Codec<AmountString> => codecForString(); + +/** + * Result of a possibly overflowing operation. + */ +export interface Result { + /** + * Resulting, possibly saturated amount. + */ + amount: AmountJson; + /** + * Was there an over-/underflow? + */ + saturated: boolean; +} + +/** + * Get an amount that represents zero units of a currency. + */ +export function getZero(currency: string): AmountJson { + return { + currency, + fraction: 0, + value: 0, + }; +} + +export type AmountLike = AmountString | AmountJson; + +export function jsonifyAmount(amt: AmountLike): AmountJson { + if (typeof amt === "string") { + return parseOrThrow(amt); + } + return amt; +} + +export function sum(amounts: AmountLike[]): Result { + if (amounts.length <= 0) { + throw Error("can't sum zero amounts"); + } + const jsonAmounts = amounts.map((x) => jsonifyAmount(x)); + return add(jsonAmounts[0], ...jsonAmounts.slice(1)); +} + +/** + * Add two amounts. Return the result and whether + * the addition overflowed. The overflow is always handled + * by saturating and never by wrapping. + * + * Throws when currencies don't match. + */ +export function add(first: AmountJson, ...rest: AmountJson[]): Result { + const currency = first.currency; + let value = first.value + Math.floor(first.fraction / fractionalBase); + if (value > maxAmountValue) { + return { + amount: { currency, value: maxAmountValue, fraction: fractionalBase - 1 }, + saturated: true, + }; + } + let fraction = first.fraction % fractionalBase; + for (const x of rest) { + if (x.currency !== currency) { + throw Error(`Mismatched currency: ${x.currency} and ${currency}`); + } + + value = + value + x.value + Math.floor((fraction + x.fraction) / fractionalBase); + fraction = Math.floor((fraction + x.fraction) % fractionalBase); + if (value > maxAmountValue) { + return { + amount: { + currency, + value: maxAmountValue, + fraction: fractionalBase - 1, + }, + saturated: true, + }; + } + } + return { amount: { currency, value, fraction }, saturated: false }; +} + +/** + * Subtract two amounts. Return the result and whether + * the subtraction overflowed. The overflow is always handled + * by saturating and never by wrapping. + * + * Throws when currencies don't match. + */ +export function sub(a: AmountJson, ...rest: AmountJson[]): Result { + const currency = a.currency; + let value = a.value; + let fraction = a.fraction; + + for (const b of rest) { + if (b.currency !== currency) { + throw Error(`Mismatched currency: ${b.currency} and ${currency}`); + } + if (fraction < b.fraction) { + if (value < 1) { + return { amount: { currency, value: 0, fraction: 0 }, saturated: true }; + } + value--; + fraction += fractionalBase; + } + console.assert(fraction >= b.fraction); + fraction -= b.fraction; + if (value < b.value) { + return { amount: { currency, value: 0, fraction: 0 }, saturated: true }; + } + value -= b.value; + } + + return { amount: { currency, value, fraction }, saturated: false }; +} + +/** + * Compare two amounts. Returns 0 when equal, -1 when a < b + * and +1 when a > b. Throws when currencies don't match. + */ +export function cmp(a: AmountLike, b: AmountLike): -1 | 0 | 1 { + a = jsonifyAmount(a); + b = jsonifyAmount(b); + if (a.currency !== b.currency) { + throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); + } + const av = a.value + Math.floor(a.fraction / fractionalBase); + const af = a.fraction % fractionalBase; + const bv = b.value + Math.floor(b.fraction / fractionalBase); + const bf = b.fraction % fractionalBase; + switch (true) { + case av < bv: + return -1; + case av > bv: + return 1; + case af < bf: + return -1; + case af > bf: + return 1; + case af === bf: + return 0; + default: + throw Error("assertion failed"); + } +} + +/** + * Create a copy of an amount. + */ +export function copy(a: AmountJson): AmountJson { + return { + currency: a.currency, + fraction: a.fraction, + value: a.value, + }; +} + +/** + * Divide an amount. Throws on division by zero. + */ +export function divide(a: AmountJson, n: number): AmountJson { + if (n === 0) { + throw Error(`Division by 0`); + } + if (n === 1) { + return { value: a.value, fraction: a.fraction, currency: a.currency }; + } + const r = a.value % n; + return { + currency: a.currency, + fraction: Math.floor((r * fractionalBase + a.fraction) / n), + value: Math.floor(a.value / n), + }; +} + +/** + * Check if an amount is non-zero. + */ +export function isNonZero(a: AmountJson): boolean { + return a.value > 0 || a.fraction > 0; +} + +export function isZero(a: AmountLike): boolean { + a = jsonifyAmount(a); + return a.value === 0 && a.fraction === 0; +} + +/** + * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct. + */ +export function parse(s: string): AmountJson | undefined { + const res = s.match(/^([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?$/); + if (!res) { + return undefined; + } + const tail = res[3] || ".0"; + if (tail.length > fractionalLength + 1) { + return undefined; + } + const value = Number.parseInt(res[2]); + if (value > maxAmountValue) { + return undefined; + } + return { + currency: res[1], + fraction: Math.round(fractionalBase * Number.parseFloat(tail)), + value, + }; +} + +/** + * Parse amount in standard string form (like 'EUR:20.5'), + * throw if the input is not a valid amount. + */ +export function parseOrThrow(s: string): AmountJson { + const res = parse(s); + if (!res) { + throw Error(`Can't parse amount: "${s}"`); + } + return res; +} + +/** + * Convert a float to a Taler amount. + * Loss of precision possible. + */ +export function fromFloat(floatVal: number, currency: string): AmountJson { + return { + currency, + fraction: Math.floor((floatVal - Math.floor(floatVal)) * fractionalBase), + value: Math.floor(floatVal), + }; +} + +/** + * Convert to standard human-readable string representation that's + * also used in JSON formats. + */ +export function stringify(a: AmountLike): string { + a = jsonifyAmount(a); + const av = a.value + Math.floor(a.fraction / fractionalBase); + const af = a.fraction % fractionalBase; + let s = av.toString(); + + if (af) { + s = s + "."; + let n = af; + for (let i = 0; i < fractionalLength; i++) { + if (!n) { + break; + } + s = s + Math.floor((n / fractionalBase) * 10).toString(); + n = (n * 10) % fractionalBase; + } + } + + return `${a.currency}:${s}`; +} + +/** + * Check if the argument is a valid amount in string form. + */ +function check(a: any): boolean { + if (typeof a !== "string") { + return false; + } + try { + const parsedAmount = parse(a); + return !!parsedAmount; + } catch { + return false; + } +} + +function mult(a: AmountJson, n: number): Result { + if (!Number.isInteger(n)) { + throw Error("amount can only be multipied by an integer"); + } + if (n < 0) { + throw Error("amount can only be multiplied by a positive integer"); + } + if (n == 0) { + return { amount: getZero(a.currency), saturated: false }; + } + let x = a; + let acc = getZero(a.currency); + while (n > 1) { + if (n % 2 == 0) { + n = n / 2; + } else { + n = (n - 1) / 2; + const r2 = add(acc, x); + if (r2.saturated) { + return r2; + } + acc = r2.amount; + } + const r2 = add(x, x); + if (r2.saturated) { + return r2; + } + x = r2.amount; + } + return add(acc, x); +} + +function max(a: AmountLike, b: AmountLike): AmountJson { + const cr = Amounts.cmp(a, b); + if (cr >= 0) { + return jsonifyAmount(a); + } else { + return jsonifyAmount(b); + } +} + +function min(a: AmountLike, b: AmountLike): AmountJson { + const cr = Amounts.cmp(a, b); + if (cr >= 0) { + return jsonifyAmount(b); + } else { + return jsonifyAmount(a); + } +} + + +// Export all amount-related functions here for better IDE experience. +export const Amounts = { + stringify: stringify, + parse: parse, + parseOrThrow: parseOrThrow, + cmp: cmp, + add: add, + sum: sum, + sub: sub, + mult: mult, + max: max, + min: min, + check: check, + getZero: getZero, + isZero: isZero, + maxAmountValue: maxAmountValue, + fromFloat: fromFloat, + copy: copy, + fractionalBase: fractionalBase, + divide: divide, +}; diff --git a/packages/taler-util/src/backupTypes.ts b/packages/taler-util/src/backupTypes.ts @@ -0,0 +1,1306 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Type declarations for the backup content format. + * + * Contains some redundancy with the other type declarations, + * as the backup schema must remain very stable and should be self-contained. + * + * Future: + * 1. Ghost spends (coin unexpectedly spent by a wallet with shared data) + * 2. Ghost withdrawals (reserve unexpectedly emptied by another wallet with shared data) + * 3. Track losses through re-denomination of payments/refreshes + * 4. (Feature:) Payments to own bank account and P2P-payments need to be backed up + * 5. Track last/next update time, so on restore we need to do less work + * 6. Currency render preferences? + * + * Questions: + * 1. What happens when two backups are merged that have + * the same coin in different refresh groups? + * => Both are added, one will eventually fail + * 2. Should we make more information forgettable? I.e. is + * the coin selection still relevant for a purchase after the coins + * are legally expired? + * => Yes, still needs to be implemented + * 3. What about re-denominations / re-selection of payment coins? + * Is it enough to store a clock value for the selection? + * => Coin derivation should also consider denom pub hash + * + * General considerations / decisions: + * 1. Information about previously occurring errors and + * retries is never backed up. + * 2. The ToS text of an exchange is never backed up. + * 3. Derived information is never backed up (hashed values, public keys + * when we know the private key). + * + * @author Florian Dold <dold@taler.net> + */ + +/** + * Imports. + */ +import { Duration, Timestamp } from "./time.js"; + +/** + * Type alias for strings that are to be treated like amounts. + */ +type BackupAmountString = string; + +/** + * A human-recognizable identifier here that is + * reasonable unique and assigned the first time the wallet is + * started/installed, such as: + * + * `${wallet-implementation} ${os} ${hostname} (${short-uid})` + * => e.g. "GNU Taler Android iceking ABC123" + */ +type DeviceIdString = string; + +/** + * Lamport clock timestamp. + */ +export interface ClockStamp { + deviceId: string; + value: number; +} + +/** + * Contract terms JSON. + */ +type RawContractTerms = any; + +/** + * Content of the backup. + * + * The contents of the wallet must be serialized in a deterministic + * way across implementations, so that the normalized backup content + * JSON is identical when the wallet's content is identical. + */ +export interface WalletBackupContentV1 { + /** + * Magic constant to identify that this is a backup content JSON. + */ + schema_id: "gnu-taler-wallet-backup-content"; + + /** + * Version of the schema. + */ + schema_version: 1; + + /** + * Root public key of the wallet. This field is present as + * a sanity check if the backup content JSON is loaded from file. + */ + wallet_root_pub: string; + + /** + * Current device identifier that "owns" the backup. + * + * This identifier allows one wallet to notice when another + * wallet is "alive" and connected to the same sync provider. + */ + current_device_id: DeviceIdString; + + /** + * Monotonically increasing clock of the wallet, + * used to determine causality when merging backups. + * + * Information about other clocks, used to delete + * tombstones in the hopefully rare case that multiple wallets + * are connected to the same sync server. + */ + clocks: { [device_id: string]: number }; + + /** + * Timestamp of the backup. + * + * This timestamp should only be advanced if the content + * of the backup changes. + */ + timestamp: Timestamp; + + /** + * Per-exchange data sorted by exchange master public key. + * + * Sorted by the exchange public key. + */ + exchanges: BackupExchange[]; + + /** + * Grouped refresh sessions. + * + * Sorted by the refresh group ID. + */ + refresh_groups: BackupRefreshGroup[]; + + /** + * Tips. + * + * Sorted by the wallet tip ID. + */ + tips: BackupTip[]; + + /** + * Proposals from merchants. The proposal may + * be deleted as soon as it has been accepted (and thus + * turned into a purchase). + * + * Sorted by the proposal ID. + */ + proposals: BackupProposal[]; + + /** + * Accepted purchases. + * + * Sorted by the proposal ID. + */ + purchases: BackupPurchase[]; + + /** + * All backup providers. + * + * Sorted by the provider base URL. + */ + backup_providers: BackupBackupProvider[]; + + /** + * Recoup groups. + */ + recoup_groups: BackupRecoupGroup[]; + + /** + * Tombstones for deleting purchases. + */ + purchase_tombstones: { + /** + * Clock when the purchase was deleted + */ + clock_deleted: ClockStamp; + + /** + * Proposal ID identifying the purchase. + */ + proposal_id: string; + }[]; + + /** + * Trusted auditors, either for official (3 letter) or local (4-12 letter) + * currencies. + * + * Auditors are sorted by their canonicalized base URL. + */ + trusted_auditors: { [currency: string]: BackupTrustAuditor[] }; + + /** + * Trusted exchange. Only applicable for local currencies (4-12 letter currency code). + * + * Exchanges are sorted by their canonicalized base URL. + */ + trusted_exchanges: { [currency: string]: BackupTrustExchange[] }; + + /** + * Interning table for forgettable values of contract terms. + * + * Used to reduce storage space, as many forgettable items (product image, + * addresses, etc.) might be shared among many contract terms. + */ + intern_table: { [hash: string]: any }; + + /** + * Permanent error reports. + */ + error_reports: BackupErrorReport[]; +} + +/** + * Detailed error report. + * + * For auditor-relevant reports with attached cryptographic proof, + * the error report also should contain the submission status to + * the auditor(s). + */ +interface BackupErrorReport { + // FIXME: specify! +} + +/** + * Trust declaration for an auditor. + * + * The trust applies based on the public key of + * the auditor, irrespective of what base URL the exchange + * is referencing. + */ +export interface BackupTrustAuditor { + /** + * Base URL of the auditor. + */ + auditor_base_url: string; + + /** + * Public key of the auditor. + */ + auditor_pub: string; + + /** + * Clock when the auditor trust has been added. + * + * Can be undefined if this entry represents a removal delta + * from the wallet's defaults. + */ + clock_added?: ClockStamp; + + /** + * Clock for when the auditor trust has been removed. + */ + clock_removed?: ClockStamp; +} + +/** + * Trust declaration for an exchange. + * + * The trust only applies for the combination of base URL + * and public key. If the master public key changes while the base + * URL stays the same, the exchange has to be re-added by a wallet update + * or by the user. + */ +export interface BackupTrustExchange { + /** + * Canonicalized exchange base URL. + */ + exchange_base_url: string; + + /** + * Master public key of the exchange. + */ + exchange_master_pub: string; + + /** + * Clock when the exchange trust has been added. + * + * Can be undefined if this entry represents a removal delta + * from the wallet's defaults. + */ + clock_added?: ClockStamp; + + /** + * Clock for when the exchange trust has been removed. + */ + clock_removed?: ClockStamp; +} + +export class BackupBackupProviderTerms { + /** + * Last known supported protocol version. + */ + supported_protocol_version: string; + + /** + * Last known annual fee. + */ + annual_fee: BackupAmountString; + + /** + * Last known storage limit. + */ + storage_limit_in_megabytes: number; +} + +/** + * Backup information about one backup storage provider. + */ +export class BackupBackupProvider { + /** + * Canonicalized base URL of the provider. + */ + base_url: string; + + /** + * Last known terms. Might be unavailable in some situations, such + * as directly after restoring form a backup recovery document. + */ + terms?: BackupBackupProviderTerms; + + /** + * Proposal IDs for payments to this provider. + */ + pay_proposal_ids: string[]; +} + +/** + * Status of recoup operations that were grouped together. + * + * The remaining amount of the corresponding coins must be set to + * zero when the recoup group is created/imported. + */ +export interface BackupRecoupGroup { + /** + * Unique identifier for the recoup group record. + */ + recoup_group_id: string; + + /** + * Timestamp when the recoup was started. + */ + timestamp_created: Timestamp; + + timestamp_finish?: Timestamp; + finish_clock?: Timestamp; + finish_is_failure?: boolean; + + /** + * Information about each coin being recouped. + */ + coins: { + coin_pub: string; + recoup_finished: boolean; + old_amount: BackupAmountString; + }[]; +} + +/** + * Types of coin sources. + */ +export enum BackupCoinSourceType { + Withdraw = "withdraw", + Refresh = "refresh", + Tip = "tip", +} + +/** + * Metadata about a coin obtained via withdrawing. + */ +export interface BackupWithdrawCoinSource { + type: BackupCoinSourceType.Withdraw; + + /** + * Can be the empty string for orphaned coins. + */ + withdrawal_group_id: string; + + /** + * Index of the coin in the withdrawal session. + */ + coin_index: number; + + /** + * Reserve public key for the reserve we got this coin from. + */ + reserve_pub: string; +} + +/** + * Metadata about a coin obtained from refreshing. + * + * FIXME: Currently does not link to the refreshGroupId because + * the wallet DB doesn't do this. Not really necessary, + * but would be more consistent. + */ +export interface BackupRefreshCoinSource { + type: BackupCoinSourceType.Refresh; + + /** + * Public key of the coin that was refreshed into this coin. + */ + old_coin_pub: string; +} + +/** + * Metadata about a coin obtained from a tip. + */ +export interface BackupTipCoinSource { + type: BackupCoinSourceType.Tip; + + /** + * Wallet's identifier for the tip that this coin + * originates from. + */ + wallet_tip_id: string; + + /** + * Index in the tip planchets of the tip. + */ + coin_index: number; +} + +/** + * Metadata about a coin depending on the origin. + */ +export type BackupCoinSource = + | BackupWithdrawCoinSource + | BackupRefreshCoinSource + | BackupTipCoinSource; + +/** + * Backup information about a coin. + * + * (Always part of a BackupExchange/BackupDenom) + */ +export interface BackupCoin { + /** + * Where did the coin come from? Used for recouping coins. + */ + coin_source: BackupCoinSource; + + /** + * Private key to authorize operations on the coin. + */ + coin_priv: string; + + /** + * Unblinded signature by the exchange. + */ + denom_sig: string; + + /** + * Amount that's left on the coin. + */ + current_amount: BackupAmountString; + + /** + * Blinding key used when withdrawing the coin. + * Potentionally used again during payback. + */ + blinding_key: string; + + /** + * Does the wallet think that the coin is still fresh? + * + * Note that even if a fresh coin is imported, it should still + * be refreshed in most situations. + */ + fresh: boolean; + + /** + * Clock for the last update to current_amount/fresh. + */ + last_clock?: ClockStamp; +} + +/** + * Status of a tip we got from a merchant. + */ +export interface BackupTip { + /** + * Tip ID chosen by the wallet. + */ + wallet_tip_id: string; + + /** + * The merchant's identifier for this tip. + */ + merchant_tip_id: string; + + /** + * Secret seed used for the tipping planchets. + */ + secret_seed: string; + + /** + * Has the user accepted the tip? Only after the tip has been accepted coins + * withdrawn from the tip may be used. + */ + timestamp_accepted: Timestamp | undefined; + + /** + * When was the tip first scanned by the wallet? + */ + timestamp_created: Timestamp; + + timestamp_finished?: Timestamp; + finish_clock?: ClockStamp; + finish_is_failure?: boolean; + + /** + * The tipped amount. + */ + tip_amount_raw: BackupAmountString; + + /** + * Timestamp, the tip can't be picked up anymore after this deadline. + */ + timestamp_expiration: Timestamp; + + /** + * The exchange that will sign our coins, chosen by the merchant. + */ + exchange_base_url: string; + + /** + * Base URL of the merchant that is giving us the tip. + */ + merchant_base_url: string; + + /** + * Selected denominations. Determines the effective tip amount. + */ + selected_denoms: BackupDenomSel; + + selected_denoms_clock?: ClockStamp; +} + +/** + * Reasons for why a coin is being refreshed. + */ +export enum BackupRefreshReason { + Manual = "manual", + Pay = "pay", + Refund = "refund", + AbortPay = "abort-pay", + Recoup = "recoup", + BackupRestored = "backup-restored", + Scheduled = "scheduled", +} + +/** + * Information about one refresh session, always part + * of a refresh group. + * + * (Public key of the old coin is stored in the refresh group.) + */ +export interface BackupRefreshSession { + /** + * Hashed denominations of the newly requested coins. + */ + new_denoms: BackupDenomSel; + + /** + * Seed used to derive the planchets and + * transfer private keys for this refresh session. + */ + session_secret_seed: string; + + /** + * The no-reveal-index after we've done the melting. + */ + noreveal_index?: number; +} + +/** + * Refresh session for one coin inside a refresh group. + */ +export interface BackupRefreshOldCoin { + /** + * Public key of the old coin, + */ + coin_pub: string; + + /** + * Requested amount to refresh. Must be subtracted from the coin's remaining + * amount as soon as the coin is added to the refresh group. + */ + input_amount: BackupAmountString; + + /** + * Estimated output (may change if it takes a long time to create the + * actual session). + */ + estimated_output_amount: BackupAmountString; + + /** + * Did the refresh session finish (or was it unnecessary/impossible to create + * one) + */ + finished: boolean; + + /** + * Refresh session (if created) or undefined it not created yet. + */ + refresh_session: BackupRefreshSession | undefined; +} + +/** + * Information about one refresh group. + * + * May span more than one exchange, but typically doesn't + */ +export interface BackupRefreshGroup { + refresh_group_id: string; + + reason: BackupRefreshReason; + + /** + * Details per old coin. + */ + old_coins: BackupRefreshOldCoin[]; + + timestamp_created: Timestamp; + + timestamp_finish?: Timestamp; + finish_clock?: ClockStamp; + finish_is_failure?: boolean; +} + +/** + * Backup information for a withdrawal group. + * + * Always part of a BackupReserve. + */ +export interface BackupWithdrawalGroup { + withdrawal_group_id: string; + + /** + * Secret seed to derive the planchets. + */ + secret_seed: string; + + /** + * When was the withdrawal operation started started? + * Timestamp in milliseconds. + */ + timestamp_created: Timestamp; + + timestamp_finish?: Timestamp; + finish_clock?: ClockStamp; + finish_is_failure?: boolean; + + /** + * Amount including fees (i.e. the amount subtracted from the + * reserve to withdraw all coins in this withdrawal session). + * + * Note that this *includes* the amount remaining in the reserve + * that is too small to be withdrawn, and thus can't be derived + * from selectedDenoms. + */ + raw_withdrawal_amount: BackupAmountString; + + /** + * Multiset of denominations selected for withdrawal. + */ + selected_denoms: BackupDenomSel; + + selected_denoms_clock?: ClockStamp; +} + +export enum BackupRefundState { + Failed = "failed", + Applied = "applied", + Pending = "pending", +} + +/** + * Common information about a refund. + */ +export interface BackupRefundItemCommon { + /** + * Execution time as claimed by the merchant + */ + execution_time: Timestamp; + + /** + * Time when the wallet became aware of the refund. + */ + obtained_time: Timestamp; + + /** + * Amount refunded for the coin. + */ + refund_amount: BackupAmountString; + + /** + * Coin being refunded. + */ + coin_pub: string; + + /** + * The refund transaction ID for the refund. + */ + rtransaction_id: number; + + /** + * Upper bound on the refresh cost incurred by + * applying this refund. + * + * Might be lower in practice when two refunds on the same + * coin are refreshed in the same refresh operation. + * + * Used to display fees, and stored since it's expensive to recompute + * accurately. + */ + total_refresh_cost_bound: BackupAmountString; + + last_clock?: ClockStamp; +} + +/** + * Failed refund, either because the merchant did + * something wrong or it expired. + */ +export interface BackupRefundFailedItem extends BackupRefundItemCommon { + type: BackupRefundState.Failed; +} + +export interface BackupRefundPendingItem extends BackupRefundItemCommon { + type: BackupRefundState.Pending; +} + +export interface BackupRefundAppliedItem extends BackupRefundItemCommon { + type: BackupRefundState.Applied; +} + +/** + * State of one refund from the merchant, maintained by the wallet. + */ +export type BackupRefundItem = + | BackupRefundFailedItem + | BackupRefundPendingItem + | BackupRefundAppliedItem; + +export interface BackupPurchase { + /** + * Proposal ID for this purchase. Uniquely identifies the + * purchase and the proposal. + */ + proposal_id: string; + + /** + * Contract terms we got from the merchant. + */ + contract_terms_raw: RawContractTerms; + + /** + * Signature on the contract terms. + */ + merchant_sig: string; + + /** + * Private key for the nonce. Might eventually be used + * to prove ownership of the contract. + */ + nonce_priv: string; + + pay_coins: { + /** + * Public keys of the coins that were selected. + */ + coin_pub: string; + + /** + * Amount that each coin contributes. + */ + contribution: BackupAmountString; + }[]; + + /** + * Clock when the pay coin selection was made/updated. + */ + pay_coins_clock?: ClockStamp; + + /** + * Total cost initially shown to the user. + * + * This includes the amount taken by the merchant, fees (wire/deposit) contributed + * by the customer, refreshing fees, fees for withdraw-after-refresh and "trimmings" + * of coins that are too small to spend. + * + * Note that in rare situations, this cost might not be accurate (e.g. + * when the payment or refresh gets re-denominated). + * We might show adjustments to this later, but currently we don't do so. + */ + total_pay_cost: BackupAmountString; + + /** + * Timestamp of the first time that sending a payment to the merchant + * for this purchase was successful. + */ + timestamp_first_successful_pay: Timestamp | undefined; + + /** + * Signature by the merchant confirming the payment. + */ + merchant_pay_sig: string | undefined; + + /** + * When was the purchase made? + * Refers to the time that the user accepted. + */ + timestamp_accept: Timestamp; + + /** + * Pending refunds for the purchase. A refund is pending + * when the merchant reports a transient error from the exchange. + */ + refunds: BackupRefundItem[]; + + /** + * Is the purchase considered defunct (either during payment + * or during abort if abort_status is set). + */ + defunct?: boolean; + + /** + * Clock for last update to defunct status. + */ + defunct_clock?: ClockStamp; + + /** + * Abort status of the payment. + */ + abort_status?: "abort-refund" | "abort-finished"; + + /** + * Continue querying the refund status until this deadline has expired. + */ + auto_refund_deadline: Timestamp | undefined; +} + +/** + * Info about one denomination in the backup. + * + * Note that the wallet only backs up validated denominations. + */ +export interface BackupDenomination { + /** + * Value of one coin of the denomination. + */ + value: BackupAmountString; + + /** + * The denomination public key. + */ + denom_pub: string; + + /** + * Fee for withdrawing. + */ + fee_withdraw: BackupAmountString; + + /** + * Fee for depositing. + */ + fee_deposit: BackupAmountString; + + /** + * Fee for refreshing. + */ + fee_refresh: BackupAmountString; + + /** + * Fee for refunding. + */ + fee_refund: BackupAmountString; + + /** + * Validity start date of the denomination. + */ + stamp_start: Timestamp; + + /** + * Date after which the currency can't be withdrawn anymore. + */ + stamp_expire_withdraw: Timestamp; + + /** + * Date after the denomination officially doesn't exist anymore. + */ + stamp_expire_legal: Timestamp; + + /** + * Data after which coins of this denomination can't be deposited anymore. + */ + stamp_expire_deposit: Timestamp; + + /** + * Signature by the exchange's master key over the denomination + * information. + */ + master_sig: string; + + /** + * Was this denomination still offered by the exchange the last time + * we checked? + * Only false when the exchange redacts a previously published denomination. + */ + is_offered: boolean; + + /** + * Did the exchange revoke the denomination? + * When this field is set to true in the database, the same transaction + * should also mark all affected coins as revoked. + */ + is_revoked: boolean; + + /** + * Coins of this denomination. + */ + coins: BackupCoin[]; +} + +/** + * Denomination selection. + */ +export type BackupDenomSel = { + denom_pub_hash: string; + count: number; +}[]; + +export interface BackupReserve { + /** + * The reserve private key. + */ + reserve_priv: string; + + /** + * Time when the reserve was created. + */ + timestamp_created: Timestamp; + + /** + * Timestamp of the last observed activity. + * + * Used to compute when to give up querying the exchange. + */ + timestamp_last_activity: Timestamp; + + /** + * Timestamp of when the reserve closed. + * + * Note that the last activity can be after the closing time + * due to recouping. + */ + timestamp_closed?: Timestamp; + + /** + * Wire information (as payto URI) for the bank account that + * transfered funds for this reserve. + */ + sender_wire?: string; + + /** + * Amount that was sent by the user to fund the reserve. + */ + instructed_amount: BackupAmountString; + + /** + * Extra state for when this is a withdrawal involving + * a Taler-integrated bank. + */ + bank_info?: { + /** + * Status URL that the wallet will use to query the status + * of the Taler withdrawal operation on the bank's side. + */ + status_url: string; + + /** + * URL that the user should be instructed to navigate to + * in order to confirm the transfer (or show instructions/help + * on how to do that at a PoS terminal). + */ + confirm_url?: string; + + /** + * Exchange payto URI that the bank will use to fund the reserve. + */ + exchange_payto_uri: string; + + /** + * Time when the information about this reserve was posted to the bank. + */ + timestamp_reserve_info_posted: Timestamp | undefined; + + /** + * Time when the reserve was confirmed by the bank. + * + * Set to undefined if not confirmed yet. + */ + timestamp_bank_confirmed: Timestamp | undefined; + }; + + /** + * Pre-allocated withdrawal group ID that will be + * used for the first withdrawal. + * + * (Already created so it can be referenced in the transactions list + * before it really exists, as there'll be an entry for the withdrawal + * even before the withdrawal group really has been created). + */ + initial_withdrawal_group_id: string; + + /** + * Denominations selected for the initial withdrawal. + * Stored here to show costs before withdrawal has begun. + */ + initial_selected_denoms: BackupDenomSel; + + /** + * Groups of withdrawal operations for this reserve. Typically just one. + */ + withdrawal_groups: BackupWithdrawalGroup[]; + + defective?: boolean; + defective_clock?: ClockStamp; +} + +/** + * Wire fee for one wire payment target type as stored in the + * wallet's database. + * + * (Flattened to a list to make the declaration simpler). + */ +export interface BackupExchangeWireFee { + wire_type: string; + + /** + * Fee for wire transfers. + */ + wire_fee: string; + + /** + * Fees to close and refund a reserve. + */ + closing_fee: string; + + /** + * Start date of the fee. + */ + start_stamp: Timestamp; + + /** + * End date of the fee. + */ + end_stamp: Timestamp; + + /** + * Signature made by the exchange master key. + */ + sig: string; +} + +/** + * Structure of one exchange signing key in the /keys response. + */ +export class BackupExchangeSignKey { + stamp_start: Timestamp; + stamp_expire: Timestamp; + stamp_end: Timestamp; + key: string; + master_sig: string; +} + +/** + * Signature by the auditor that a particular denomination key is audited. + */ +export class BackupAuditorDenomSig { + /** + * Denomination public key's hash. + */ + denom_pub_h: string; + + /** + * The signature. + */ + auditor_sig: string; +} + +/** + * Auditor information as given by the exchange in /keys. + */ +export class BackupExchangeAuditor { + /** + * Auditor's public key. + */ + auditor_pub: string; + + /** + * Base URL of the auditor. + */ + auditor_url: string; + + /** + * List of signatures for denominations by the auditor. + */ + denomination_keys: BackupAuditorDenomSig[]; +} + +/** + * Backup information about an exchange. + */ +export interface BackupExchange { + /** + * Canonicalized base url of the exchange. + */ + base_url: string; + + /** + * Master public key of the exchange. + */ + master_public_key: string; + + /** + * Auditors (partially) auditing the exchange. + */ + auditors: BackupExchangeAuditor[]; + + /** + * Currency that the exchange offers. + */ + currency: string; + + /** + * Denominations offered by the exchange. + */ + denominations: BackupDenomination[]; + + /** + * Reserves at the exchange. + */ + reserves: BackupReserve[]; + + /** + * Last observed protocol version. + */ + protocol_version: string; + + /** + * Closing delay of reserves. + */ + reserve_closing_delay: Duration; + + /** + * Signing keys we got from the exchange, can also contain + * older signing keys that are not returned by /keys anymore. + */ + signing_keys: BackupExchangeSignKey[]; + + wire_fees: BackupExchangeWireFee[]; + + /** + * Bank accounts offered by the exchange; + */ + accounts: { + payto_uri: string; + master_sig: string; + }[]; + + /** + * ETag for last terms of service download. + */ + tos_etag_last: string | undefined; + + /** + * ETag for last terms of service download. + */ + tos_etag_accepted: string | undefined; + + /** + * Clock value of the last update. + */ + last_clock?: ClockStamp; + + /** + * Should this exchange be considered defective? + */ + defective?: boolean; + + defective_clock?: ClockStamp; +} + +export enum BackupProposalStatus { + /** + * Proposed (and either downloaded or not, + * depending on whether contract terms are present), + * but the user needs to accept/reject it. + */ + Proposed = "proposed", + /** + * The user has rejected the proposal. + */ + Refused = "refused", + /** + * Downloading or processing the proposal has failed permanently. + * + * FIXME: Should this be modeled as a "misbehavior report" instead? + */ + PermanentlyFailed = "permanently-failed", + /** + * Downloaded proposal was detected as a re-purchase. + */ + Repurchase = "repurchase", +} + +/** + * Proposal by a merchant. + */ +export interface BackupProposal { + /** + * Base URL of the merchant that proposed the purchase. + */ + merchant_base_url: string; + + /** + * Downloaded data from the merchant. + */ + contract_terms_raw?: RawContractTerms; + + /** + * Signature on the contract terms. + * + * Must be present if contract_terms_raw is present. + */ + merchant_sig?: string; + + /** + * Unique ID when the order is stored in the wallet DB. + */ + proposal_id: string; + + /** + * Merchant-assigned order ID of the proposal. + */ + order_id: string; + + /** + * Timestamp of when the record + * was created. + */ + timestamp: Timestamp; + + /** + * Private key for the nonce. + */ + nonce_priv: string; + + /** + * Claim token initially given by the merchant. + */ + claim_token: string | undefined; + + /** + * Status of the proposal. + */ + proposal_status: BackupProposalStatus; + + proposal_status_clock?: ClockStamp; + + /** + * Proposal that this one got "redirected" to as part of + * the repurchase detection. + */ + repurchase_proposal_id: string | undefined; + + /** + * Session ID we got when downloading the contract. + */ + download_session_id?: string; +} + +export interface BackupRecovery { + walletRootPriv: string; + providers: { + url: string; + }[]; +} +\ No newline at end of file diff --git a/packages/taler-wallet-core/src/util/codec-test.ts b/packages/taler-util/src/codec.test.ts diff --git a/packages/taler-wallet-core/src/util/codec.ts b/packages/taler-util/src/codec.ts diff --git a/packages/taler-wallet-core/src/util/helpers.ts b/packages/taler-util/src/helpers.ts diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts @@ -0,0 +1,19 @@ +import { TalerErrorCode } from "./taler-error-codes.js"; + +export { TalerErrorCode }; + +export * from "./codec.js"; +export * from "./amounts.js"; +export * from "./talerconfig.js"; +export * from "./time.js"; +export * from "./walletTypes"; +export * from "./transactionsTypes"; +export * from "./notifications"; +export * from "./talerTypes"; +export * from "./talerconfig"; +export * from "./taleruri"; +export * from "./ReserveStatus"; +export * from "./ReserveTransaction"; +export * from "./backupTypes"; +export * from "./payto.js"; +export * from "./libtool-version"; +\ No newline at end of file diff --git a/packages/taler-util/src/libtool-version.test.ts b/packages/taler-util/src/libtool-version.test.ts @@ -0,0 +1,48 @@ +/* + This file is part of TALER + (C) 2017 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import * as LibtoolVersion from "./libtool-version"; + +import test from "ava"; + +test("version comparison", (t) => { + t.deepEqual(LibtoolVersion.compare("0:0:0", "0:0:0"), { + compatible: true, + currentCmp: 0, + }); + t.deepEqual(LibtoolVersion.compare("0:0:0", ""), undefined); + t.deepEqual(LibtoolVersion.compare("foo", "0:0:0"), undefined); + t.deepEqual(LibtoolVersion.compare("0:0:0", "1:0:1"), { + compatible: true, + currentCmp: -1, + }); + t.deepEqual(LibtoolVersion.compare("0:0:0", "1:5:1"), { + compatible: true, + currentCmp: -1, + }); + t.deepEqual(LibtoolVersion.compare("0:0:0", "1:5:0"), { + compatible: false, + currentCmp: -1, + }); + t.deepEqual(LibtoolVersion.compare("1:0:0", "0:5:0"), { + compatible: false, + currentCmp: 1, + }); + t.deepEqual(LibtoolVersion.compare("1:0:1", "1:5:1"), { + compatible: true, + currentCmp: 0, + }); +}); diff --git a/packages/taler-wallet-core/src/util/libtoolVersion.ts b/packages/taler-util/src/libtool-version.ts diff --git a/packages/taler-wallet-core/src/types/notifications.ts b/packages/taler-util/src/notifications.ts diff --git a/packages/taler-wallet-core/src/util/payto-test.ts b/packages/taler-util/src/payto.test.ts diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts @@ -0,0 +1,71 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { URLSearchParams } from "./url.js"; + +interface PaytoUri { + targetType: string; + targetPath: string; + params: { [name: string]: string }; +} + +const paytoPfx = "payto://"; + +/** + * Add query parameters to a payto URI + */ +export function addPaytoQueryParams( + s: string, + params: { [name: string]: string }, +): string { + const [acct, search] = s.slice(paytoPfx.length).split("?"); + const searchParams = new URLSearchParams(search || ""); + for (const k of Object.keys(params)) { + searchParams.set(k, params[k]); + } + return paytoPfx + acct + "?" + searchParams.toString(); +} + +export function parsePaytoUri(s: string): PaytoUri | undefined { + if (!s.startsWith(paytoPfx)) { + return undefined; + } + + const [acct, search] = s.slice(paytoPfx.length).split("?"); + + const firstSlashPos = acct.indexOf("/"); + + if (firstSlashPos === -1) { + return undefined; + } + + const targetType = acct.slice(0, firstSlashPos); + const targetPath = acct.slice(firstSlashPos + 1); + + const params: { [k: string]: string } = {}; + + const searchParams = new URLSearchParams(search || ""); + + searchParams.forEach((v, k) => { + params[v] = k; + }); + + return { + targetPath, + targetType, + params, + }; +} diff --git a/packages/taler-wallet-core/src/TalerErrorCode.ts b/packages/taler-util/src/taler-error-codes.ts diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts @@ -0,0 +1,1457 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Type and schema definitions and helpers for the core GNU Taler protocol. + * + * All types here should be "@Checkable". + * + * Even though the rest of the wallet uses camelCase for fields, use snake_case + * here, since that's the convention for the Taler JSON+HTTP API. + */ + +/** + * Imports. + */ + +import { + buildCodecForObject, + codecForString, + codecForList, + codecOptional, + codecForAny, + codecForNumber, + codecForBoolean, + codecForMap, + Codec, + codecForConstNumber, + buildCodecForUnion, + codecForConstString, +} from "./codec"; +import { + Timestamp, + codecForTimestamp, + Duration, + codecForDuration, +} from "./time"; +import { codecForAmountString } from "./amounts"; + +/** + * Denomination as found in the /keys response from the exchange. + */ +export class Denomination { + /** + * Value of one coin of the denomination. + */ + value: string; + + /** + * Public signing key of the denomination. + */ + denom_pub: string; + + /** + * Fee for withdrawing. + */ + fee_withdraw: string; + + /** + * Fee for depositing. + */ + fee_deposit: string; + + /** + * Fee for refreshing. + */ + fee_refresh: string; + + /** + * Fee for refunding. + */ + fee_refund: string; + + /** + * Start date from which withdraw is allowed. + */ + stamp_start: Timestamp; + + /** + * End date for withdrawing. + */ + stamp_expire_withdraw: Timestamp; + + /** + * Expiration date after which the exchange can forget about + * the currency. + */ + stamp_expire_legal: Timestamp; + + /** + * Date after which the coins of this denomination can't be + * deposited anymore. + */ + stamp_expire_deposit: Timestamp; + + /** + * Signature over the denomination information by the exchange's master + * signing key. + */ + master_sig: string; +} + +/** + * Signature by the auditor that a particular denomination key is audited. + */ +export class AuditorDenomSig { + /** + * Denomination public key's hash. + */ + denom_pub_h: string; + + /** + * The signature. + */ + auditor_sig: string; +} + +/** + * Auditor information as given by the exchange in /keys. + */ +export class Auditor { + /** + * Auditor's public key. + */ + auditor_pub: string; + + /** + * Base URL of the auditor. + */ + auditor_url: string; + + /** + * List of signatures for denominations by the auditor. + */ + denomination_keys: AuditorDenomSig[]; +} + +/** + * Request that we send to the exchange to get a payback. + */ +export interface RecoupRequest { + /** + * Hashed enomination public key of the coin we want to get + * paid back. + */ + denom_pub_hash: string; + + /** + * Signature over the coin public key by the denomination. + */ + denom_sig: string; + + /** + * Coin public key of the coin we want to refund. + */ + coin_pub: string; + + /** + * Blinding key that was used during withdraw, + * used to prove that we were actually withdrawing the coin. + */ + coin_blind_key_secret: string; + + /** + * Signature made by the coin, authorizing the payback. + */ + coin_sig: string; + + /** + * Was the coin refreshed (and thus the recoup should go to the old coin)? + */ + refreshed: boolean; +} + +/** + * Response that we get from the exchange for a payback request. + */ +export class RecoupConfirmation { + /** + * Public key of the reserve that will receive the payback. + */ + reserve_pub?: string; + + /** + * Public key of the old coin that will receive the recoup, + * provided if refreshed was true. + */ + old_coin_pub?: string; +} + +/** + * Deposit permission for a single coin. + */ +export interface CoinDepositPermission { + /** + * Signature by the coin. + */ + coin_sig: string; + /** + * Public key of the coin being spend. + */ + coin_pub: string; + /** + * Signature made by the denomination public key. + */ + ub_sig: string; + /** + * The denomination public key associated with this coin. + */ + h_denom: string; + /** + * The amount that is subtracted from this coin with this payment. + */ + contribution: string; + + /** + * URL of the exchange this coin was withdrawn from. + */ + exchange_url: string; +} + +/** + * Information about an exchange as stored inside a + * merchant's contract terms. + */ +export class ExchangeHandle { + /** + * Master public signing key of the exchange. + */ + master_pub: string; + + /** + * Base URL of the exchange. + */ + url: string; +} + +export class AuditorHandle { + /** + * Official name of the auditor. + */ + name: string; + + /** + * Master public signing key of the auditor. + */ + auditor_pub: string; + + /** + * Base URL of the auditor. + */ + url: string; +} + +// Delivery location, losely modeled as a subset of +// ISO20022's PostalAddress25. +export interface Location { + // Nation with its own government. + country?: string; + + // Identifies a subdivision of a country such as state, region, county. + country_subdivision?: string; + + // Identifies a subdivision within a country sub-division. + district?: string; + + // Name of a built-up area, with defined boundaries, and a local government. + town?: string; + + // Specific location name within the town. + town_location?: string; + + // Identifier consisting of a group of letters and/or numbers that + // is added to a postal address to assist the sorting of mail. + post_code?: string; + + // Name of a street or thoroughfare. + street?: string; + + // Name of the building or house. + building_name?: string; + + // Number that identifies the position of a building on a street. + building_number?: string; + + // Free-form address lines, should not exceed 7 elements. + address_lines?: string[]; +} + +export interface MerchantInfo { + name: string; + jurisdiction?: Location; + address?: Location; +} + +export interface Tax { + // the name of the tax + name: string; + + // amount paid in tax + tax: AmountString; +} + +export interface Product { + // merchant-internal identifier for the product. + product_id?: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions + description_i18n?: { [lang_tag: string]: string }; + + // The number of units of the product to deliver to the customer. + quantity?: number; + + // The unit in which the product is measured (liters, kilograms, packages, etc.) + unit?: string; + + // The price of the product; this is the total price for quantity times unit of this product. + price?: AmountString; + + // An optional base64-encoded product image + image?: string; + + // a list of taxes paid by the merchant for this product. Can be empty. + taxes?: Tax[]; + + // time indicating when this product should be delivered + delivery_date?: Timestamp; +} + +export interface InternationalizedString { + [lang_tag: string]: string; +} + +/** + * Contract terms from a merchant. + */ +export class ContractTerms { + /** + * Hash of the merchant's wire details. + */ + h_wire: string; + + /** + * Hash of the merchant's wire details. + */ + auto_refund?: Duration; + + /** + * Wire method the merchant wants to use. + */ + wire_method: string; + + /** + * Human-readable short summary of the contract. + */ + summary: string; + + summary_i18n?: InternationalizedString; + + /** + * Nonce used to ensure freshness. + */ + nonce: string; + + /** + * Total amount payable. + */ + amount: string; + + /** + * Auditors accepted by the merchant. + */ + auditors: AuditorHandle[]; + + /** + * Deadline to pay for the contract. + */ + pay_deadline: Timestamp; + + /** + * Maximum deposit fee covered by the merchant. + */ + max_fee: string; + + /** + * Information about the merchant. + */ + merchant: MerchantInfo; + + /** + * Public key of the merchant. + */ + merchant_pub: string; + + /** + * Time indicating when the order should be delivered. + * May be overwritten by individual products. + */ + delivery_date?: Timestamp; + + /** + * Delivery location for (all!) products. + */ + delivery_location?: Location; + + /** + * List of accepted exchanges. + */ + exchanges: ExchangeHandle[]; + + /** + * Products that are sold in this contract. + */ + products?: Product[]; + + /** + * Deadline for refunds. + */ + refund_deadline: Timestamp; + + /** + * Deadline for the wire transfer. + */ + wire_transfer_deadline: Timestamp; + + /** + * Time when the contract was generated by the merchant. + */ + timestamp: Timestamp; + + /** + * Order id to uniquely identify the purchase within + * one merchant instance. + */ + order_id: string; + + /** + * Base URL of the merchant's backend. + */ + merchant_base_url: string; + + /** + * Fulfillment URL to view the product or + * delivery status. + */ + fulfillment_url?: string; + + /** + * Plain text fulfillment message in the merchant's default language. + */ + fulfillment_message?: string; + + /** + * Internationalized fulfillment messages. + */ + fulfillment_message_i18n?: InternationalizedString; + + /** + * Share of the wire fee that must be settled with one payment. + */ + wire_fee_amortization?: number; + + /** + * Maximum wire fee that the merchant agrees to pay for. + */ + max_wire_fee?: string; + + /** + * Extra data, interpreted by the mechant only. + */ + extra?: any; +} + +/** + * Refund permission in the format that the merchant gives it to us. + */ +export class MerchantAbortPayRefundDetails { + /** + * Amount to be refunded. + */ + refund_amount: string; + + /** + * Fee for the refund. + */ + refund_fee: string; + + /** + * Public key of the coin being refunded. + */ + coin_pub: string; + + /** + * Refund transaction ID between merchant and exchange. + */ + rtransaction_id: number; + + /** + * Exchange's key used for the signature. + */ + exchange_pub?: string; + + /** + * Exchange's signature to confirm the refund. + */ + exchange_sig?: string; + + /** + * Error replay from the exchange (if any). + */ + exchange_reply?: any; + + /** + * Error code from the exchange (if any). + */ + exchange_code?: number; + + /** + * HTTP status code of the exchange's response + * to the merchant's refund request. + */ + exchange_http_status: number; +} + +/** + * Response for a refund pickup or a /pay in abort mode. + */ +export class MerchantRefundResponse { + /** + * Public key of the merchant + */ + merchant_pub: string; + + /** + * Contract terms hash of the contract that + * is being refunded. + */ + h_contract_terms: string; + + /** + * The signed refund permissions, to be sent to the exchange. + */ + refunds: MerchantAbortPayRefundDetails[]; +} + +/** + * Planchet detail sent to the merchant. + */ +export interface TipPlanchetDetail { + /** + * Hashed denomination public key. + */ + denom_pub_hash: string; + + /** + * Coin's blinded public key. + */ + coin_ev: string; +} + +/** + * Request sent to the merchant to pick up a tip. + */ +export interface TipPickupRequest { + /** + * Identifier of the tip. + */ + tip_id: string; + + /** + * List of planchets the wallet wants to use for the tip. + */ + planchets: TipPlanchetDetail[]; +} + +/** + * Reserve signature, defined as separate class to facilitate + * schema validation with "@Checkable". + */ +export class BlindSigWrapper { + /** + * Reserve signature. + */ + blind_sig: string; +} + +/** + * Response of the merchant + * to the TipPickupRequest. + */ +export class TipResponse { + /** + * The order of the signatures matches the planchets list. + */ + blind_sigs: BlindSigWrapper[]; +} + +/** + * Element of the payback list that the + * exchange gives us in /keys. + */ +export class Recoup { + /** + * The hash of the denomination public key for which the payback is offered. + */ + h_denom_pub: string; +} + +/** + * Structure of one exchange signing key in the /keys response. + */ +export class ExchangeSignKeyJson { + stamp_start: Timestamp; + stamp_expire: Timestamp; + stamp_end: Timestamp; + key: EddsaPublicKeyString; + master_sig: EddsaSignatureString; +} + +/** + * Structure that the exchange gives us in /keys. + */ +export class ExchangeKeysJson { + /** + * List of offered denominations. + */ + denoms: Denomination[]; + + /** + * The exchange's master public key. + */ + master_public_key: string; + + /** + * The list of auditors (partially) auditing the exchange. + */ + auditors: Auditor[]; + + /** + * Timestamp when this response was issued. + */ + list_issue_date: Timestamp; + + /** + * List of revoked denominations. + */ + recoup?: Recoup[]; + + /** + * Short-lived signing keys used to sign online + * responses. + */ + signkeys: ExchangeSignKeyJson[]; + + /** + * Protocol version. + */ + version: string; + + reserve_closing_delay: Duration; +} + +/** + * Wire fees as anounced by the exchange. + */ +export class WireFeesJson { + /** + * Cost of a wire transfer. + */ + wire_fee: string; + + /** + * Cost of clising a reserve. + */ + closing_fee: string; + + /** + * Signature made with the exchange's master key. + */ + sig: string; + + /** + * Date from which the fee applies. + */ + start_date: Timestamp; + + /** + * Data after which the fee doesn't apply anymore. + */ + end_date: Timestamp; +} + +export class AccountInfo { + payto_uri: string; + master_sig: string; +} + +export class ExchangeWireJson { + accounts: AccountInfo[]; + fees: { [methodName: string]: WireFeesJson[] }; +} + +/** + * Proposal returned from the contract URL. + */ +export class Proposal { + /** + * Contract terms for the propoal. + * Raw, un-decoded JSON object. + */ + contract_terms: any; + + /** + * Signature over contract, made by the merchant. The public key used for signing + * must be contract_terms.merchant_pub. + */ + sig: string; +} + +/** + * Response from the internal merchant API. + */ +export class CheckPaymentResponse { + order_status: string; + refunded: boolean | undefined; + refunded_amount: string | undefined; + contract_terms: any | undefined; + taler_pay_uri: string | undefined; + contract_url: string | undefined; +} + +/** + * Response from the bank. + */ +export class WithdrawOperationStatusResponse { + selection_done: boolean; + + transfer_done: boolean; + + aborted: boolean; + + amount: string; + + sender_wire?: string; + + suggested_exchange?: string; + + confirm_transfer_url?: string; + + wire_types: string[]; +} + +/** + * Response from the merchant. + */ +export class TipPickupGetResponse { + tip_amount: string; + + exchange_url: string; + + expiration: Timestamp; +} + +export class WithdrawResponse { + ev_sig: string; +} + +/** + * Easy to process format for the public data of coins + * managed by the wallet. + */ +export interface CoinDumpJson { + coins: Array<{ + /** + * The coin's denomination's public key. + */ + denom_pub: string; + /** + * Hash of denom_pub. + */ + denom_pub_hash: string; + /** + * Value of the denomination (without any fees). + */ + denom_value: string; + /** + * Public key of the coin. + */ + coin_pub: string; + /** + * Base URL of the exchange for the coin. + */ + exchange_base_url: string; + /** + * Remaining value on the coin, to the knowledge of + * the wallet. + */ + remaining_value: string; + /** + * Public key of the parent coin. + * Only present if this coin was obtained via refreshing. + */ + refresh_parent_coin_pub: string | undefined; + /** + * Public key of the reserve for this coin. + * Only present if this coin was obtained via refreshing. + */ + withdrawal_reserve_pub: string | undefined; + /** + * Is the coin suspended? + * Suspended coins are not considered for payments. + */ + coin_suspended: boolean; + }>; +} + +export interface MerchantPayResponse { + sig: string; +} + +export interface ExchangeMeltResponse { + /** + * Which of the kappa indices does the client not have to reveal. + */ + noreveal_index: number; + + /** + * Signature of TALER_RefreshMeltConfirmationPS whereby the exchange + * affirms the successful melt and confirming the noreveal_index + */ + exchange_sig: EddsaSignatureString; + + /* + * public EdDSA key of the exchange that was used to generate the signature. + * Should match one of the exchange's signing keys from /keys. Again given + * explicitly as the client might otherwise be confused by clock skew as to + * which signing key was used. + */ + exchange_pub: EddsaPublicKeyString; + + /* + * Base URL to use for operations on the refresh context + * (so the reveal operation). If not given, + * the base URL is the same as the one used for this request. + * Can be used if the base URL for /refreshes/ differs from that + * for /coins/, i.e. for load balancing. Clients SHOULD + * respect the refresh_base_url if provided. Any HTTP server + * belonging to an exchange MUST generate a 307 or 308 redirection + * to the correct base URL should a client uses the wrong base + * URL, or if the base URL has changed since the melt. + * + * When melting the same coin twice (technically allowed + * as the response might have been lost on the network), + * the exchange may return different values for the refresh_base_url. + */ + refresh_base_url?: string; +} + +export interface ExchangeRevealItem { + ev_sig: string; +} + +export interface ExchangeRevealResponse { + // List of the exchange's blinded RSA signatures on the new coins. + ev_sigs: ExchangeRevealItem[]; +} + +interface MerchantOrderStatusPaid { + /** + * Was the payment refunded (even partially, via refund or abort)? + */ + refunded: boolean; + + /** + * Amount that was refunded in total. + */ + refund_amount: AmountString; +} + +interface MerchantOrderRefundResponse { + /** + * Amount that was refunded in total. + */ + refund_amount: AmountString; + + /** + * Successful refunds for this payment, empty array for none. + */ + refunds: MerchantCoinRefundStatus[]; + + /** + * Public key of the merchant. + */ + merchant_pub: EddsaPublicKeyString; +} + +export type MerchantCoinRefundStatus = + | MerchantCoinRefundSuccessStatus + | MerchantCoinRefundFailureStatus; + +export interface MerchantCoinRefundSuccessStatus { + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: EddsaSignatureString; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: Timestamp; +} + +export interface MerchantCoinRefundFailureStatus { + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: any; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: Timestamp; +} + +export interface MerchantOrderStatusUnpaid { + /** + * URI that the wallet must process to complete the payment. + */ + taler_pay_uri: string; + + /** + * Alternative order ID which was paid for already in the same session. + * + * Only given if the same product was purchased before in the same session. + */ + already_paid_order_id?: string; +} + +/** + * Response body for the following endpoint: + * + * POST {talerBankIntegrationApi}/withdrawal-operation/{wopid} + */ +export interface BankWithdrawalOperationPostResponse { + transfer_done: boolean; +} + +export const codecForBankWithdrawalOperationPostResponse = (): Codec< + BankWithdrawalOperationPostResponse +> => + buildCodecForObject<BankWithdrawalOperationPostResponse>() + .property("transfer_done", codecForBoolean()) + .build("BankWithdrawalOperationPostResponse"); + +export type AmountString = string; +export type Base32String = string; +export type EddsaSignatureString = string; +export type EddsaPublicKeyString = string; +export type CoinPublicKeyString = string; + +export const codecForDenomination = (): Codec<Denomination> => + buildCodecForObject<Denomination>() + .property("value", codecForString()) + .property("denom_pub", codecForString()) + .property("fee_withdraw", codecForString()) + .property("fee_deposit", codecForString()) + .property("fee_refresh", codecForString()) + .property("fee_refund", codecForString()) + .property("stamp_start", codecForTimestamp) + .property("stamp_expire_withdraw", codecForTimestamp) + .property("stamp_expire_legal", codecForTimestamp) + .property("stamp_expire_deposit", codecForTimestamp) + .property("master_sig", codecForString()) + .build("Denomination"); + +export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => + buildCodecForObject<AuditorDenomSig>() + .property("denom_pub_h", codecForString()) + .property("auditor_sig", codecForString()) + .build("AuditorDenomSig"); + +export const codecForAuditor = (): Codec<Auditor> => + buildCodecForObject<Auditor>() + .property("auditor_pub", codecForString()) + .property("auditor_url", codecForString()) + .property("denomination_keys", codecForList(codecForAuditorDenomSig())) + .build("Auditor"); + +export const codecForExchangeHandle = (): Codec<ExchangeHandle> => + buildCodecForObject<ExchangeHandle>() + .property("master_pub", codecForString()) + .property("url", codecForString()) + .build("ExchangeHandle"); + +export const codecForAuditorHandle = (): Codec<AuditorHandle> => + buildCodecForObject<AuditorHandle>() + .property("name", codecForString()) + .property("auditor_pub", codecForString()) + .property("url", codecForString()) + .build("AuditorHandle"); + +export const codecForLocation = (): Codec<Location> => + buildCodecForObject<Location>() + .property("country", codecOptional(codecForString())) + .property("country_subdivision", codecOptional(codecForString())) + .property("building_name", codecOptional(codecForString())) + .property("building_number", codecOptional(codecForString())) + .property("district", codecOptional(codecForString())) + .property("street", codecOptional(codecForString())) + .property("post_code", codecOptional(codecForString())) + .property("town", codecOptional(codecForString())) + .property("town_location", codecOptional(codecForString())) + .property("address_lines", codecOptional(codecForList(codecForString()))) + .build("Location"); + +export const codecForMerchantInfo = (): Codec<MerchantInfo> => + buildCodecForObject<MerchantInfo>() + .property("name", codecForString()) + .property("address", codecOptional(codecForLocation())) + .property("jurisdiction", codecOptional(codecForLocation())) + .build("MerchantInfo"); + +export const codecForTax = (): Codec<Tax> => + buildCodecForObject<Tax>() + .property("name", codecForString()) + .property("tax", codecForString()) + .build("Tax"); + +export const codecForInternationalizedString = (): Codec< + InternationalizedString +> => codecForMap(codecForString()); + +export const codecForProduct = (): Codec<Product> => + buildCodecForObject<Product>() + .property("product_id", codecOptional(codecForString())) + .property("description", codecForString()) + .property( + "description_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("quantity", codecOptional(codecForNumber())) + .property("unit", codecOptional(codecForString())) + .property("price", codecOptional(codecForString())) + .build("Tax"); + +export const codecForContractTerms = (): Codec<ContractTerms> => + buildCodecForObject<ContractTerms>() + .property("order_id", codecForString()) + .property("fulfillment_url", codecOptional(codecForString())) + .property("fulfillment_message", codecOptional(codecForString())) + .property( + "fulfillment_message_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("merchant_base_url", codecForString()) + .property("h_wire", codecForString()) + .property("auto_refund", codecOptional(codecForDuration)) + .property("wire_method", codecForString()) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .property("nonce", codecForString()) + .property("amount", codecForString()) + .property("auditors", codecForList(codecForAuditorHandle())) + .property("pay_deadline", codecForTimestamp) + .property("refund_deadline", codecForTimestamp) + .property("wire_transfer_deadline", codecForTimestamp) + .property("timestamp", codecForTimestamp) + .property("delivery_location", codecOptional(codecForLocation())) + .property("delivery_date", codecOptional(codecForTimestamp)) + .property("max_fee", codecForString()) + .property("max_wire_fee", codecOptional(codecForString())) + .property("merchant", codecForMerchantInfo()) + .property("merchant_pub", codecForString()) + .property("exchanges", codecForList(codecForExchangeHandle())) + .property("products", codecOptional(codecForList(codecForProduct()))) + .property("extra", codecForAny()) + .build("ContractTerms"); + +export const codecForMerchantRefundPermission = (): Codec< + MerchantAbortPayRefundDetails +> => + buildCodecForObject<MerchantAbortPayRefundDetails>() + .property("refund_amount", codecForAmountString()) + .property("refund_fee", codecForAmountString()) + .property("coin_pub", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("exchange_sig", codecOptional(codecForString())) + .property("exchange_pub", codecOptional(codecForString())) + .build("MerchantRefundPermission"); + +export const codecForMerchantRefundResponse = (): Codec< + MerchantRefundResponse +> => + buildCodecForObject<MerchantRefundResponse>() + .property("merchant_pub", codecForString()) + .property("h_contract_terms", codecForString()) + .property("refunds", codecForList(codecForMerchantRefundPermission())) + .build("MerchantRefundResponse"); + +export const codecForBlindSigWrapper = (): Codec<BlindSigWrapper> => + buildCodecForObject<BlindSigWrapper>() + .property("blind_sig", codecForString()) + .build("BlindSigWrapper"); + +export const codecForTipResponse = (): Codec<TipResponse> => + buildCodecForObject<TipResponse>() + .property("blind_sigs", codecForList(codecForBlindSigWrapper())) + .build("TipResponse"); + +export const codecForRecoup = (): Codec<Recoup> => + buildCodecForObject<Recoup>() + .property("h_denom_pub", codecForString()) + .build("Recoup"); + +export const codecForExchangeSigningKey = (): Codec<ExchangeSignKeyJson> => + buildCodecForObject<ExchangeSignKeyJson>() + .property("key", codecForString()) + .property("master_sig", codecForString()) + .property("stamp_end", codecForTimestamp) + .property("stamp_start", codecForTimestamp) + .property("stamp_expire", codecForTimestamp) + .build("ExchangeSignKeyJson"); + +export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => + buildCodecForObject<ExchangeKeysJson>() + .property("denoms", codecForList(codecForDenomination())) + .property("master_public_key", codecForString()) + .property("auditors", codecForList(codecForAuditor())) + .property("list_issue_date", codecForTimestamp) + .property("recoup", codecOptional(codecForList(codecForRecoup()))) + .property("signkeys", codecForList(codecForExchangeSigningKey())) + .property("version", codecForString()) + .property("reserve_closing_delay", codecForDuration) + .build("KeysJson"); + +export const codecForWireFeesJson = (): Codec<WireFeesJson> => + buildCodecForObject<WireFeesJson>() + .property("wire_fee", codecForString()) + .property("closing_fee", codecForString()) + .property("sig", codecForString()) + .property("start_date", codecForTimestamp) + .property("end_date", codecForTimestamp) + .build("WireFeesJson"); + +export const codecForAccountInfo = (): Codec<AccountInfo> => + buildCodecForObject<AccountInfo>() + .property("payto_uri", codecForString()) + .property("master_sig", codecForString()) + .build("AccountInfo"); + +export const codecForExchangeWireJson = (): Codec<ExchangeWireJson> => + buildCodecForObject<ExchangeWireJson>() + .property("accounts", codecForList(codecForAccountInfo())) + .property("fees", codecForMap(codecForList(codecForWireFeesJson()))) + .build("ExchangeWireJson"); + +export const codecForProposal = (): Codec<Proposal> => + buildCodecForObject<Proposal>() + .property("contract_terms", codecForAny()) + .property("sig", codecForString()) + .build("Proposal"); + +export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> => + buildCodecForObject<CheckPaymentResponse>() + .property("order_status", codecForString()) + .property("refunded", codecOptional(codecForBoolean())) + .property("refunded_amount", codecOptional(codecForString())) + .property("contract_terms", codecOptional(codecForAny())) + .property("taler_pay_uri", codecOptional(codecForString())) + .property("contract_url", codecOptional(codecForString())) + .build("CheckPaymentResponse"); + +export const codecForWithdrawOperationStatusResponse = (): Codec< + WithdrawOperationStatusResponse +> => + buildCodecForObject<WithdrawOperationStatusResponse>() + .property("selection_done", codecForBoolean()) + .property("transfer_done", codecForBoolean()) + .property("aborted", codecForBoolean()) + .property("amount", codecForString()) + .property("sender_wire", codecOptional(codecForString())) + .property("suggested_exchange", codecOptional(codecForString())) + .property("confirm_transfer_url", codecOptional(codecForString())) + .property("wire_types", codecForList(codecForString())) + .build("WithdrawOperationStatusResponse"); + +export const codecForTipPickupGetResponse = (): Codec<TipPickupGetResponse> => + buildCodecForObject<TipPickupGetResponse>() + .property("tip_amount", codecForString()) + .property("exchange_url", codecForString()) + .property("expiration", codecForTimestamp) + .build("TipPickupGetResponse"); + +export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> => + buildCodecForObject<RecoupConfirmation>() + .property("reserve_pub", codecOptional(codecForString())) + .property("old_coin_pub", codecOptional(codecForString())) + .build("RecoupConfirmation"); + +export const codecForWithdrawResponse = (): Codec<WithdrawResponse> => + buildCodecForObject<WithdrawResponse>() + .property("ev_sig", codecForString()) + .build("WithdrawResponse"); + +export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => + buildCodecForObject<MerchantPayResponse>() + .property("sig", codecForString()) + .build("MerchantPayResponse"); + +export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> => + buildCodecForObject<ExchangeMeltResponse>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("noreveal_index", codecForNumber()) + .property("refresh_base_url", codecOptional(codecForString())) + .build("ExchangeMeltResponse"); + +export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> => + buildCodecForObject<ExchangeRevealItem>() + .property("ev_sig", codecForString()) + .build("ExchangeRevealItem"); + +export const codecForExchangeRevealResponse = (): Codec< + ExchangeRevealResponse +> => + buildCodecForObject<ExchangeRevealResponse>() + .property("ev_sigs", codecForList(codecForExchangeRevealItem())) + .build("ExchangeRevealResponse"); + +export const codecForMerchantCoinRefundSuccessStatus = (): Codec< + MerchantCoinRefundSuccessStatus +> => + buildCodecForObject<MerchantCoinRefundSuccessStatus>() + .property("type", codecForConstString("success")) + .property("coin_pub", codecForString()) + .property("exchange_status", codecForConstNumber(200)) + .property("exchange_sig", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("refund_amount", codecForString()) + .property("exchange_pub", codecForString()) + .property("execution_time", codecForTimestamp) + .build("MerchantCoinRefundSuccessStatus"); + +export const codecForMerchantCoinRefundFailureStatus = (): Codec< + MerchantCoinRefundFailureStatus +> => + buildCodecForObject<MerchantCoinRefundFailureStatus>() + .property("type", codecForConstString("failure")) + .property("coin_pub", codecForString()) + .property("exchange_status", codecForNumber()) + .property("rtransaction_id", codecForNumber()) + .property("refund_amount", codecForString()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("execution_time", codecForTimestamp) + .build("MerchantCoinRefundFailureStatus"); + +export const codecForMerchantCoinRefundStatus = (): Codec< + MerchantCoinRefundStatus +> => + buildCodecForUnion<MerchantCoinRefundStatus>() + .discriminateOn("type") + .alternative("success", codecForMerchantCoinRefundSuccessStatus()) + .alternative("failure", codecForMerchantCoinRefundFailureStatus()) + .build("MerchantCoinRefundStatus"); + +export const codecForMerchantOrderStatusPaid = (): Codec< + MerchantOrderStatusPaid +> => + buildCodecForObject<MerchantOrderStatusPaid>() + .property("refund_amount", codecForString()) + .property("refunded", codecForBoolean()) + .build("MerchantOrderStatusPaid"); + +export const codecForMerchantOrderRefundPickupResponse = (): Codec< + MerchantOrderRefundResponse +> => + buildCodecForObject<MerchantOrderRefundResponse>() + .property("merchant_pub", codecForString()) + .property("refund_amount", codecForString()) + .property("refunds", codecForList(codecForMerchantCoinRefundStatus())) + .build("MerchantOrderRefundPickupResponse"); + +export const codecForMerchantOrderStatusUnpaid = (): Codec< + MerchantOrderStatusUnpaid +> => + buildCodecForObject<MerchantOrderStatusUnpaid>() + .property("taler_pay_uri", codecForString()) + .property("already_paid_order_id", codecOptional(codecForString())) + .build("MerchantOrderStatusUnpaid"); + +export interface AbortRequest { + // hash of the order's contract terms (this is used to authenticate the + // wallet/customer in case $ORDER_ID is guessable). + h_contract: string; + + // List of coins the wallet would like to see refunds for. + // (Should be limited to the coins for which the original + // payment succeeded, as far as the wallet knows.) + coins: AbortingCoin[]; +} + +export interface AbortingCoin { + // Public key of a coin for which the wallet is requesting an abort-related refund. + coin_pub: EddsaPublicKeyString; + + // The amount to be refunded (matches the original contribution) + contribution: AmountString; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; +} + +export interface AbortResponse { + // List of refund responses about the coins that the wallet + // requested an abort for. In the same order as the 'coins' + // from the original request. + // The rtransaction_id is implied to be 0. + refunds: MerchantAbortPayRefundStatus[]; +} + +export const codecForAbortResponse = (): Codec<AbortResponse> => + buildCodecForObject<AbortResponse>() + .property("refunds", codecForList(codecForMerchantAbortPayRefundStatus())) + .build("AbortResponse"); + +export type MerchantAbortPayRefundStatus = + | MerchantAbortPayRefundSuccessStatus + | MerchantAbortPayRefundFailureStatus; + +// Details about why a refund failed. +export interface MerchantAbortPayRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: unknown; +} + +// Additional details needed to verify the refund confirmation signature +// (h_contract_terms and merchant_pub) are already known +// to the wallet and thus not included. +export interface MerchantAbortPayRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: string; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: string; +} + +export const codecForMerchantAbortPayRefundSuccessStatus = (): Codec< + MerchantAbortPayRefundSuccessStatus +> => + buildCodecForObject<MerchantAbortPayRefundSuccessStatus>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("exchange_status", codecForConstNumber(200)) + .property("type", codecForConstString("success")) + .build("MerchantAbortPayRefundSuccessStatus"); + +export const codecForMerchantAbortPayRefundFailureStatus = (): Codec< + MerchantAbortPayRefundFailureStatus +> => + buildCodecForObject<MerchantAbortPayRefundFailureStatus>() + .property("exchange_code", codecForNumber()) + .property("exchange_reply", codecForAny()) + .property("exchange_status", codecForNumber()) + .property("type", codecForConstString("failure")) + .build("MerchantAbortPayRefundFailureStatus"); + +export const codecForMerchantAbortPayRefundStatus = (): Codec< + MerchantAbortPayRefundStatus +> => + buildCodecForUnion<MerchantAbortPayRefundStatus>() + .discriminateOn("type") + .alternative("success", codecForMerchantAbortPayRefundSuccessStatus()) + .alternative("failure", codecForMerchantAbortPayRefundFailureStatus()) + .build("MerchantAbortPayRefundStatus"); + +export interface TalerConfigResponse { + name: string; + version: string; + currency?: string; +} + +export const codecForTalerConfigResponse = (): Codec<TalerConfigResponse> => + buildCodecForObject<TalerConfigResponse>() + .property("name", codecForString()) + .property("version", codecForString()) + .property("currency", codecOptional(codecForString())) + .build("TalerConfigResponse"); diff --git a/packages/taler-wallet-core/src/util/talerconfig-test.ts b/packages/taler-util/src/talerconfig.test.ts diff --git a/packages/taler-wallet-core/src/util/talerconfig.ts b/packages/taler-util/src/talerconfig.ts diff --git a/packages/taler-wallet-core/src/util/taleruri-test.ts b/packages/taler-util/src/taleruri.test.ts diff --git a/packages/taler-wallet-core/src/util/taleruri.ts b/packages/taler-util/src/taleruri.ts diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts @@ -0,0 +1,264 @@ +/* + This file is part of GNU Taler + (C) 2017-2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Helpers for relative and absolute time. + */ + +/** + * Imports. + */ +import { Codec, renderContext, Context } from "./codec"; + +export class Timestamp { + /** + * Timestamp in milliseconds. + */ + readonly t_ms: number | "never"; +} + +export interface Duration { + /** + * Duration in milliseconds. + */ + readonly d_ms: number | "forever"; +} + +let timeshift = 0; + +export function setDangerousTimetravel(dt: number): void { + timeshift = dt; +} + +export function getTimestampNow(): Timestamp { + return { + t_ms: new Date().getTime() + timeshift, + }; +} + +export function isTimestampExpired(t: Timestamp) { + return timestampCmp(t, getTimestampNow()) <= 0; +} + +export function getDurationRemaining( + deadline: Timestamp, + now = getTimestampNow(), +): Duration { + if (deadline.t_ms === "never") { + return { d_ms: "forever" }; + } + if (now.t_ms === "never") { + throw Error("invalid argument for 'now'"); + } + if (deadline.t_ms < now.t_ms) { + return { d_ms: 0 }; + } + return { d_ms: deadline.t_ms - now.t_ms }; +} + +export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp { + if (t1.t_ms === "never") { + return { t_ms: t2.t_ms }; + } + if (t2.t_ms === "never") { + return { t_ms: t2.t_ms }; + } + return { t_ms: Math.min(t1.t_ms, t2.t_ms) }; +} + +export function timestampMax(t1: Timestamp, t2: Timestamp): Timestamp { + if (t1.t_ms === "never") { + return { t_ms: "never" }; + } + if (t2.t_ms === "never") { + return { t_ms: "never" }; + } + return { t_ms: Math.max(t1.t_ms, t2.t_ms) }; +} + +const SECONDS = 1000; +const MINUTES = SECONDS * 60; +const HOURS = MINUTES * 60; +const DAYS = HOURS * 24; +const MONTHS = DAYS * 30; +const YEARS = DAYS * 365; + +export function durationFromSpec(spec: { + seconds?: number; + minutes?: number; + hours?: number; + days?: number; + months?: number; + years?: number; +}): Duration { + let d_ms = 0; + d_ms += (spec.seconds ?? 0) * SECONDS; + d_ms += (spec.minutes ?? 0) * MINUTES; + d_ms += (spec.hours ?? 0) * HOURS; + d_ms += (spec.days ?? 0) * DAYS; + d_ms += (spec.months ?? 0) * MONTHS; + d_ms += (spec.years ?? 0) * YEARS; + return { d_ms }; +} + +/** + * Truncate a timestamp so that that it represents a multiple + * of seconds. The timestamp is always rounded down. + */ +export function timestampTruncateToSecond(t1: Timestamp): Timestamp { + if (t1.t_ms === "never") { + return { t_ms: "never" }; + } + return { + t_ms: Math.floor(t1.t_ms / 1000) * 1000, + }; +} + +export function durationMin(d1: Duration, d2: Duration): Duration { + if (d1.d_ms === "forever") { + return { d_ms: d2.d_ms }; + } + if (d2.d_ms === "forever") { + return { d_ms: d2.d_ms }; + } + return { d_ms: Math.min(d1.d_ms, d2.d_ms) }; +} + +export function durationMax(d1: Duration, d2: Duration): Duration { + if (d1.d_ms === "forever") { + return { d_ms: "forever" }; + } + if (d2.d_ms === "forever") { + return { d_ms: "forever" }; + } + return { d_ms: Math.max(d1.d_ms, d2.d_ms) }; +} + +export function durationMul(d: Duration, n: number): Duration { + if (d.d_ms === "forever") { + return { d_ms: "forever" }; + } + return { d_ms: Math.round(d.d_ms * n) }; +} + +export function durationAdd(d1: Duration, d2: Duration): Duration { + if (d1.d_ms === "forever" || d2.d_ms === "forever") { + return { d_ms: "forever" }; + } + return { d_ms: d1.d_ms + d2.d_ms }; +} + +export function timestampCmp(t1: Timestamp, t2: Timestamp): number { + if (t1.t_ms === "never") { + if (t2.t_ms === "never") { + return 0; + } + return 1; + } + if (t2.t_ms === "never") { + return -1; + } + if (t1.t_ms == t2.t_ms) { + return 0; + } + if (t1.t_ms > t2.t_ms) { + return 1; + } + return -1; +} + +export function timestampAddDuration(t1: Timestamp, d: Duration): Timestamp { + if (t1.t_ms === "never" || d.d_ms === "forever") { + return { t_ms: "never" }; + } + return { t_ms: t1.t_ms + d.d_ms }; +} + +export function timestampSubtractDuraction( + t1: Timestamp, + d: Duration, +): Timestamp { + if (t1.t_ms === "never") { + return { t_ms: "never" }; + } + if (d.d_ms === "forever") { + return { t_ms: 0 }; + } + return { t_ms: Math.max(0, t1.t_ms - d.d_ms) }; +} + +export function stringifyTimestamp(t: Timestamp): string { + if (t.t_ms === "never") { + return "never"; + } + return new Date(t.t_ms).toISOString(); +} + +export function timestampDifference(t1: Timestamp, t2: Timestamp): Duration { + if (t1.t_ms === "never") { + return { d_ms: "forever" }; + } + if (t2.t_ms === "never") { + return { d_ms: "forever" }; + } + return { d_ms: Math.abs(t1.t_ms - t2.t_ms) }; +} + +export function timestampIsBetween( + t: Timestamp, + start: Timestamp, + end: Timestamp, +): boolean { + if (timestampCmp(t, start) < 0) { + return false; + } + if (timestampCmp(t, end) > 0) { + return false; + } + return true; +} + +export const codecForTimestamp: Codec<Timestamp> = { + decode(x: any, c?: Context): Timestamp { + const t_ms = x.t_ms; + if (typeof t_ms === "string") { + if (t_ms === "never") { + return { t_ms: "never" }; + } + throw Error(`expected timestamp at ${renderContext(c)}`); + } + if (typeof t_ms === "number") { + return { t_ms }; + } + throw Error(`expected timestamp at ${renderContext(c)}`); + }, +}; + +export const codecForDuration: Codec<Duration> = { + decode(x: any, c?: Context): Duration { + const d_ms = x.d_ms; + if (typeof d_ms === "string") { + if (d_ms === "forever") { + return { d_ms: "forever" }; + } + throw Error(`expected duration at ${renderContext(c)}`); + } + if (typeof d_ms === "number") { + return { d_ms }; + } + throw Error(`expected duration at ${renderContext(c)}`); + }, +}; diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts @@ -0,0 +1,364 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Type and schema definitions for the wallet's transaction list. + * + * @author Florian Dold + * @author Torsten Grote + */ + +/** + * Imports. + */ +import { Timestamp } from "./time.js"; +import { + AmountString, + Product, + InternationalizedString, + MerchantInfo, + codecForInternationalizedString, + codecForMerchantInfo, + codecForProduct, +} from "./talerTypes.js"; +import { + Codec, + buildCodecForObject, + codecOptional, + codecForString, + codecForList, + codecForAny, +} from "./codec.js"; +import { TalerErrorDetails } from "./walletTypes.js"; + +export interface TransactionsRequest { + /** + * return only transactions in the given currency + */ + currency?: string; + + /** + * if present, results will be limited to transactions related to the given search string + */ + search?: string; +} + +export interface TransactionsResponse { + // a list of past and pending transactions sorted by pending, timestamp and transactionId. + // In case two events are both pending and have the same timestamp, + // they are sorted by the transactionId + // (lexically ascending and locale-independent comparison). + transactions: Transaction[]; +} + +export interface TransactionCommon { + // opaque unique ID for the transaction, used as a starting point for paginating queries + // and for invoking actions on the transaction (e.g. deleting/hiding it from the history) + transactionId: string; + + // the type of the transaction; different types might provide additional information + type: TransactionType; + + // main timestamp of the transaction + timestamp: Timestamp; + + // true if the transaction is still pending, false otherwise + // If a transaction is not longer pending, its timestamp will be updated, + // but its transactionId will remain unchanged + pending: boolean; + + // Raw amount of the transaction (exclusive of fees or other extra costs) + amountRaw: AmountString; + + // Amount added or removed from the wallet's balance (including all fees and other costs) + amountEffective: AmountString; + + error?: TalerErrorDetails; +} + +export type Transaction = + | TransactionWithdrawal + | TransactionPayment + | TransactionRefund + | TransactionTip + | TransactionRefresh + | TransactionDeposit; + +export enum TransactionType { + Withdrawal = "withdrawal", + Payment = "payment", + Refund = "refund", + Refresh = "refresh", + Tip = "tip", + Deposit = "deposit", +} + +export enum WithdrawalType { + TalerBankIntegrationApi = "taler-bank-integration-api", + ManualTransfer = "manual-transfer", +} + +export type WithdrawalDetails = + | WithdrawalDetailsForManualTransfer + | WithdrawalDetailsForTalerBankIntegrationApi; + +interface WithdrawalDetailsForManualTransfer { + type: WithdrawalType.ManualTransfer; + + /** + * Payto URIs that the exchange supports. + * + * Already contains the amount and message. + */ + exchangePaytoUris: string[]; +} + +interface WithdrawalDetailsForTalerBankIntegrationApi { + type: WithdrawalType.TalerBankIntegrationApi; + + /** + * Set to true if the bank has confirmed the withdrawal, false if not. + * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI. + * See also bankConfirmationUrl below. + */ + confirmed: boolean; + + /** + * If the withdrawal is unconfirmed, this can include a URL for user + * initiated confirmation. + */ + bankConfirmationUrl?: string; +} + +// This should only be used for actual withdrawals +// and not for tips that have their own transactions type. +interface TransactionWithdrawal extends TransactionCommon { + type: TransactionType.Withdrawal; + + /** + * Exchange of the withdrawal. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + withdrawalDetails: WithdrawalDetails; +} + +export enum PaymentStatus { + /** + * Explicitly aborted after timeout / failure + */ + Aborted = "aborted", + + /** + * Payment failed, wallet will auto-retry. + * User should be given the option to retry now / abort. + */ + Failed = "failed", + + /** + * Paid successfully + */ + Paid = "paid", + + /** + * User accepted, payment is processing. + */ + Accepted = "accepted", +} + +export interface TransactionPayment extends TransactionCommon { + type: TransactionType.Payment; + + /** + * Additional information about the payment. + */ + info: OrderShortInfo; + + /** + * Wallet-internal end-to-end identifier for the payment. + */ + proposalId: string; + + /** + * How far did the wallet get with processing the payment? + */ + status: PaymentStatus; + + /** + * Amount that must be paid for the contract + */ + amountRaw: AmountString; + + /** + * Amount that was paid, including deposit, wire and refresh fees. + */ + amountEffective: AmountString; +} + +export interface OrderShortInfo { + /** + * Order ID, uniquely identifies the order within a merchant instance + */ + orderId: string; + + /** + * Hash of the contract terms. + */ + contractTermsHash: string; + + /** + * More information about the merchant + */ + merchant: MerchantInfo; + + /** + * Summary of the order, given by the merchant + */ + summary: string; + + /** + * Map from IETF BCP 47 language tags to localized summaries + */ + summary_i18n?: InternationalizedString; + + /** + * List of products that are part of the order + */ + products: Product[] | undefined; + + /** + * URL of the fulfillment, given by the merchant + */ + fulfillmentUrl?: string; + + /** + * Plain text message that should be shown to the user + * when the payment is complete. + */ + fulfillmentMessage?: string; + + /** + * Translations of fulfillmentMessage. + */ + fulfillmentMessage_i18n?: InternationalizedString; +} + +interface TransactionRefund extends TransactionCommon { + type: TransactionType.Refund; + + // ID for the transaction that is refunded + refundedTransactionId: string; + + // Additional information about the refunded payment + info: OrderShortInfo; + + // Amount that has been refunded by the merchant + amountRaw: AmountString; + + // Amount will be added to the wallet's balance after fees and refreshing + amountEffective: AmountString; +} + +interface TransactionTip extends TransactionCommon { + type: TransactionType.Tip; + + // Raw amount of the tip, without extra fees that apply + amountRaw: AmountString; + + // Amount will be (or was) added to the wallet's balance after fees and refreshing + amountEffective: AmountString; + + merchantBaseUrl: string; +} + +// A transaction shown for refreshes that are not associated to other transactions +// such as a refresh necessary before coin expiration. +// It should only be returned by the API if the effective amount is different from zero. +interface TransactionRefresh extends TransactionCommon { + type: TransactionType.Refresh; + + // Exchange that the coins are refreshed with + exchangeBaseUrl: string; + + // Raw amount that is refreshed + amountRaw: AmountString; + + // Amount that will be paid as fees for the refresh + amountEffective: AmountString; +} + +/** + * Deposit transaction, which effectively sends + * money from this wallet somewhere else. + */ +interface TransactionDeposit extends TransactionCommon { + type: TransactionType.Deposit; + + depositGroupId: string; + + /** + * Target for the deposit. + */ + targetPaytoUri: string; + + /** + * Raw amount that is being deposited + */ + amountRaw: AmountString; + + /** + * Effective amount that is being deposited + */ + amountEffective: AmountString; +} + +export const codecForTransactionsRequest = (): Codec<TransactionsRequest> => + buildCodecForObject<TransactionsRequest>() + .property("currency", codecOptional(codecForString())) + .property("search", codecOptional(codecForString())) + .build("TransactionsRequest"); + +// FIXME: do full validation here! +export const codecForTransactionsResponse = (): Codec<TransactionsResponse> => + buildCodecForObject<TransactionsResponse>() + .property("transactions", codecForList(codecForAny())) + .build("TransactionsResponse"); + +export const codecForOrderShortInfo = (): Codec<OrderShortInfo> => + buildCodecForObject<OrderShortInfo>() + .property("contractTermsHash", codecForString()) + .property("fulfillmentMessage", codecOptional(codecForString())) + .property( + "fulfillmentMessage_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("fulfillmentUrl", codecOptional(codecForString())) + .property("merchant", codecForMerchantInfo()) + .property("orderId", codecForString()) + .property("products", codecOptional(codecForList(codecForProduct()))) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .build("OrderShortInfo"); diff --git a/packages/taler-wallet-core/src/types/types-test.ts b/packages/taler-util/src/types-test.ts diff --git a/packages/taler-util/src/url.ts b/packages/taler-util/src/url.ts @@ -0,0 +1,74 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +interface URL { + hash: string; + host: string; + hostname: string; + href: string; + toString(): string; + readonly origin: string; + password: string; + pathname: string; + port: string; + protocol: string; + search: string; + readonly searchParams: URLSearchParams; + username: string; + toJSON(): string; +} + +interface URLSearchParams { + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | null; + getAll(name: string): string[]; + has(name: string): boolean; + set(name: string, value: string): void; + sort(): void; + toString(): string; + forEach( + callbackfn: (value: string, key: string, parent: URLSearchParams) => void, + thisArg?: any, + ): void; +} + +export interface URLSearchParamsCtor { + new ( + init?: string[][] | Record<string, string> | string | URLSearchParams, + ): URLSearchParams; +} + +export interface URLCtor { + new (url: string, base?: string | URL): URL; +} + +// @ts-ignore +const _URL = globalThis.URL; +if (!_URL) { + throw Error("FATAL: URL not available"); +} + +export const URL: URLCtor = _URL; + +// @ts-ignore +const _URLSearchParams = globalThis.URLSearchParams; + +if (!_URLSearchParams) { + throw Error("FATAL: URLSearchParams not available"); +} + +export const URLSearchParams: URLSearchParamsCtor = _URLSearchParams; diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts @@ -0,0 +1,936 @@ +/* + This file is part of GNU Taler + (C) 2015-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Types used by clients of the wallet. + * + * These types are defined in a separate file make tree shaking easier, since + * some components use these types (via RPC) but do not depend on the wallet + * code directly. + * + * @author Florian Dold <dold@taler.net> + */ + +/** + * Imports. + */ +import { + AmountJson, + codecForAmountJson, + codecForAmountString, +} from "./amounts.js"; +import * as LibtoolVersion from "./libtool-version.js"; +import { Timestamp, codecForTimestamp } from "./time.js"; +import { + buildCodecForObject, + codecForString, + codecOptional, + Codec, + codecForList, + codecForBoolean, + codecForConstString, + codecForAny, + buildCodecForUnion, +} from "./codec.js"; +import { + AmountString, + codecForContractTerms, + ContractTerms, +} from "./talerTypes.js"; +import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes"; +import { BackupRecovery } from "./backupTypes.js"; + +/** + * Response for the create reserve request to the wallet. + */ +export class CreateReserveResponse { + /** + * Exchange URL where the bank should create the reserve. + * The URL is canonicalized in the response. + */ + exchange: string; + + /** + * Reserve public key of the newly created reserve. + */ + reservePub: string; +} + + + +export interface Balance { + available: AmountString; + pendingIncoming: AmountString; + pendingOutgoing: AmountString; + + // Does the balance for this currency have a pending + // transaction? + hasPendingTransactions: boolean; + + // Is there a pending transaction that would affect the balance + // and requires user input? + requiresUserInput: boolean; +} + +export interface BalancesResponse { + balances: Balance[]; +} + +export const codecForBalance = (): Codec<Balance> => + buildCodecForObject<Balance>() + .property("available", codecForString()) + .property("hasPendingTransactions", codecForBoolean()) + .property("pendingIncoming", codecForString()) + .property("pendingOutgoing", codecForString()) + .property("requiresUserInput", codecForBoolean()) + .build("Balance"); + +export const codecForBalancesResponse = (): Codec<BalancesResponse> => + buildCodecForObject<BalancesResponse>() + .property("balances", codecForList(codecForBalance())) + .build("BalancesResponse"); + +/** + * For terseness. + */ +export function mkAmount( + value: number, + fraction: number, + currency: string, +): AmountJson { + return { value, fraction, currency }; +} + +export enum ConfirmPayResultType { + Done = "done", + Pending = "pending", +} + +/** + * Result for confirmPay + */ +export interface ConfirmPayResultDone { + type: ConfirmPayResultType.Done; + contractTerms: ContractTerms; +} + +export interface ConfirmPayResultPending { + type: ConfirmPayResultType.Pending; + + lastError: TalerErrorDetails; +} + +export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending; + +export const codecForConfirmPayResultPending = (): Codec<ConfirmPayResultPending> => + buildCodecForObject<ConfirmPayResultPending>() + .property("lastError", codecForAny()) + .property("type", codecForConstString(ConfirmPayResultType.Pending)) + .build("ConfirmPayResultPending"); + +export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> => + buildCodecForObject<ConfirmPayResultDone>() + .property("type", codecForConstString(ConfirmPayResultType.Done)) + .property("contractTerms", codecForContractTerms()) + .build("ConfirmPayResultDone"); + +export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> => + buildCodecForUnion<ConfirmPayResult>() + .discriminateOn("type") + .alternative( + ConfirmPayResultType.Pending, + codecForConfirmPayResultPending(), + ) + .alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone()) + .build("ConfirmPayResult"); + +/** + * Information about all sender wire details known to the wallet, + * as well as exchanges that accept these wire types. + */ +export interface SenderWireInfos { + /** + * Mapping from exchange base url to list of accepted + * wire types. + */ + exchangeWireTypes: { [exchangeBaseUrl: string]: string[] }; + + /** + * Sender wire information stored in the wallet. + */ + senderWires: string[]; +} + +/** + * Request to create a reserve. + */ +export interface CreateReserveRequest { + /** + * The initial amount for the reserve. + */ + amount: AmountJson; + + /** + * Exchange URL where the bank should create the reserve. + */ + exchange: string; + + /** + * Payto URI that identifies the exchange's account that the funds + * for this reserve go into. + */ + exchangePaytoUri?: string; + + /** + * Wire details (as a payto URI) for the bank account that sent the funds to + * the exchange. + */ + senderWire?: string; + + /** + * URL to fetch the withdraw status from the bank. + */ + bankWithdrawStatusUrl?: string; +} + +export const codecForCreateReserveRequest = (): Codec<CreateReserveRequest> => + buildCodecForObject<CreateReserveRequest>() + .property("amount", codecForAmountJson()) + .property("exchange", codecForString()) + .property("exchangePaytoUri", codecForString()) + .property("senderWire", codecOptional(codecForString())) + .property("bankWithdrawStatusUrl", codecOptional(codecForString())) + .build("CreateReserveRequest"); + +/** + * Request to mark a reserve as confirmed. + */ +export interface ConfirmReserveRequest { + /** + * Public key of then reserve that should be marked + * as confirmed. + */ + reservePub: string; +} + +export const codecForConfirmReserveRequest = (): Codec<ConfirmReserveRequest> => + buildCodecForObject<ConfirmReserveRequest>() + .property("reservePub", codecForString()) + .build("ConfirmReserveRequest"); + +/** + * Wire coins to the user's own bank account. + */ +export class ReturnCoinsRequest { + /** + * The amount to wire. + */ + amount: AmountJson; + + /** + * The exchange to take the coins from. + */ + exchange: string; + + /** + * Wire details for the bank account of the customer that will + * receive the funds. + */ + senderWire?: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => ReturnCoinsRequest; +} + +export interface PrepareTipResult { + /** + * Unique ID for the tip assigned by the wallet. + * Typically different from the merchant-generated tip ID. + */ + walletTipId: string; + + /** + * Has the tip already been accepted? + */ + accepted: boolean; + + /** + * Amount that the merchant gave. + */ + tipAmountRaw: AmountString; + + /** + * Amount that arrived at the wallet. + * Might be lower than the raw amount due to fees. + */ + tipAmountEffective: AmountString; + + /** + * Base URL of the merchant backend giving then tip. + */ + merchantBaseUrl: string; + + /** + * Base URL of the exchange that is used to withdraw the tip. + * Determined by the merchant, the wallet/user has no choice here. + */ + exchangeBaseUrl: string; + + /** + * Time when the tip will expire. After it expired, it can't be picked + * up anymore. + */ + expirationTimestamp: Timestamp; +} + +export const codecForPrepareTipResult = (): Codec<PrepareTipResult> => + buildCodecForObject<PrepareTipResult>() + .property("accepted", codecForBoolean()) + .property("tipAmountRaw", codecForAmountString()) + .property("tipAmountEffective", codecForAmountString()) + .property("exchangeBaseUrl", codecForString()) + .property("merchantBaseUrl", codecForString()) + .property("expirationTimestamp", codecForTimestamp) + .property("walletTipId", codecForString()) + .build("PrepareTipResult"); + +export interface BenchmarkResult { + time: { [s: string]: number }; + repetitions: number; +} + +export enum PreparePayResultType { + PaymentPossible = "payment-possible", + InsufficientBalance = "insufficient-balance", + AlreadyConfirmed = "already-confirmed", +} + +export const codecForPreparePayResultPaymentPossible = (): Codec<PreparePayResultPaymentPossible> => + buildCodecForObject<PreparePayResultPaymentPossible>() + .property("amountEffective", codecForAmountString()) + .property("amountRaw", codecForAmountString()) + .property("contractTerms", codecForContractTerms()) + .property("proposalId", codecForString()) + .property( + "status", + codecForConstString(PreparePayResultType.PaymentPossible), + ) + .build("PreparePayResultPaymentPossible"); + +export const codecForPreparePayResultInsufficientBalance = (): Codec<PreparePayResultInsufficientBalance> => + buildCodecForObject<PreparePayResultInsufficientBalance>() + .property("amountRaw", codecForAmountString()) + .property("contractTerms", codecForAny()) + .property("proposalId", codecForString()) + .property( + "status", + codecForConstString(PreparePayResultType.InsufficientBalance), + ) + .build("PreparePayResultInsufficientBalance"); + +export const codecForPreparePayResultAlreadyConfirmed = (): Codec<PreparePayResultAlreadyConfirmed> => + buildCodecForObject<PreparePayResultAlreadyConfirmed>() + .property( + "status", + codecForConstString(PreparePayResultType.AlreadyConfirmed), + ) + .property("amountEffective", codecForAmountString()) + .property("amountRaw", codecForAmountString()) + .property("paid", codecForBoolean()) + .property("contractTerms", codecForAny()) + .property("contractTermsHash", codecForString()) + .property("proposalId", codecForString()) + .build("PreparePayResultAlreadyConfirmed"); + +export const codecForPreparePayResult = (): Codec<PreparePayResult> => + buildCodecForUnion<PreparePayResult>() + .discriminateOn("status") + .alternative( + PreparePayResultType.AlreadyConfirmed, + codecForPreparePayResultAlreadyConfirmed(), + ) + .alternative( + PreparePayResultType.InsufficientBalance, + codecForPreparePayResultInsufficientBalance(), + ) + .alternative( + PreparePayResultType.PaymentPossible, + codecForPreparePayResultPaymentPossible(), + ) + .build("PreparePayResult"); + +export type PreparePayResult = + | PreparePayResultInsufficientBalance + | PreparePayResultAlreadyConfirmed + | PreparePayResultPaymentPossible; + +export interface PreparePayResultPaymentPossible { + status: PreparePayResultType.PaymentPossible; + proposalId: string; + contractTerms: ContractTerms; + amountRaw: string; + amountEffective: string; +} + +export interface PreparePayResultInsufficientBalance { + status: PreparePayResultType.InsufficientBalance; + proposalId: string; + contractTerms: ContractTerms; + amountRaw: string; +} + +export interface PreparePayResultAlreadyConfirmed { + status: PreparePayResultType.AlreadyConfirmed; + contractTerms: ContractTerms; + paid: boolean; + amountRaw: string; + amountEffective: string; + contractTermsHash: string; + proposalId: string; +} + +export interface BankWithdrawDetails { + selectionDone: boolean; + transferDone: boolean; + amount: AmountJson; + senderWire?: string; + suggestedExchange?: string; + confirmTransferUrl?: string; + wireTypes: string[]; + extractedStatusUrl: string; +} + +export interface AcceptWithdrawalResponse { + reservePub: string; + confirmTransferUrl?: string; +} + +/** + * Details about a purchase, including refund status. + */ +export interface PurchaseDetails { + contractTerms: Record<string, undefined>; + hasRefund: boolean; + totalRefundAmount: AmountJson; + totalRefundAndRefreshFees: AmountJson; +} + +export interface WalletDiagnostics { + walletManifestVersion: string; + walletManifestDisplayVersion: string; + errors: string[]; + firefoxIdbProblem: boolean; + dbOutdated: boolean; +} + +export interface TalerErrorDetails { + code: number; + hint: string; + message: string; + details: unknown; +} + +export interface PlanchetCreationResult { + coinPub: string; + coinPriv: string; + reservePub: string; + denomPubHash: string; + denomPub: string; + blindingKey: string; + withdrawSig: string; + coinEv: string; + coinValue: AmountJson; + coinEvHash: string; +} + +export interface PlanchetCreationRequest { + secretSeed: string; + coinIndex: number; + value: AmountJson; + feeWithdraw: AmountJson; + denomPub: string; + reservePub: string; + reservePriv: string; +} + +/** + * Reasons for why a coin is being refreshed. + */ +export enum RefreshReason { + Manual = "manual", + Pay = "pay", + Refund = "refund", + AbortPay = "abort-pay", + Recoup = "recoup", + BackupRestored = "backup-restored", + Scheduled = "scheduled", +} + +/** + * Wrapper for coin public keys. + */ +export interface CoinPublicKey { + readonly coinPub: string; +} + +/** + * Wrapper for refresh group IDs. + */ +export interface RefreshGroupId { + readonly refreshGroupId: string; +} + +/** + * Private data required to make a deposit permission. + */ +export interface DepositInfo { + exchangeBaseUrl: string; + contractTermsHash: string; + coinPub: string; + coinPriv: string; + spendAmount: AmountJson; + timestamp: Timestamp; + refundDeadline: Timestamp; + merchantPub: string; + feeDeposit: AmountJson; + wireInfoHash: string; + denomPubHash: string; + denomSig: string; +} + +export interface ExchangesListRespose { + exchanges: ExchangeListItem[]; +} + +export interface ExchangeListItem { + exchangeBaseUrl: string; + currency: string; + paytoUris: string[]; +} + +export const codecForExchangeListItem = (): Codec<ExchangeListItem> => + buildCodecForObject<ExchangeListItem>() + .property("currency", codecForString()) + .property("exchangeBaseUrl", codecForString()) + .property("paytoUris", codecForList(codecForString())) + .build("ExchangeListItem"); + +export const codecForExchangesListResponse = (): Codec<ExchangesListRespose> => + buildCodecForObject<ExchangesListRespose>() + .property("exchanges", codecForList(codecForExchangeListItem())) + .build("ExchangesListRespose"); + +export interface AcceptManualWithdrawalResult { + /** + * Payto URIs that can be used to fund the withdrawal. + */ + exchangePaytoUris: string[]; + + /** + * Public key of the newly created reserve. + */ + reservePub: string; +} + +export interface ManualWithdrawalDetails { + /** + * Did the user accept the current version of the exchange's + * terms of service? + */ + tosAccepted: boolean; + + /** + * Amount that the user will transfer to the exchange. + */ + amountRaw: AmountString; + + /** + * Amount that will be added to the user's wallet balance. + */ + amountEffective: AmountString; + + /** + * Ways to pay the exchange. + */ + paytoUris: string[]; +} + +export interface GetExchangeTosResult { + /** + * Markdown version of the current ToS. + */ + tos: string; + + /** + * Version tag of the current ToS. + */ + currentEtag: string; + + /** + * Version tag of the last ToS that the user has accepted, + * if any. + */ + acceptedEtag: string | undefined; +} + +export interface TestPayArgs { + merchantBaseUrl: string; + merchantAuthToken?: string; + amount: string; + summary: string; +} + +export const codecForTestPayArgs = (): Codec<TestPayArgs> => + buildCodecForObject<TestPayArgs>() + .property("merchantBaseUrl", codecForString()) + .property("merchantAuthToken", codecOptional(codecForString())) + .property("amount", codecForString()) + .property("summary", codecForString()) + .build("TestPayArgs"); + +export interface IntegrationTestArgs { + exchangeBaseUrl: string; + bankBaseUrl: string; + merchantBaseUrl: string; + merchantAuthToken?: string; + amountToWithdraw: string; + amountToSpend: string; +} + +export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> => + buildCodecForObject<IntegrationTestArgs>() + .property("exchangeBaseUrl", codecForString()) + .property("bankBaseUrl", codecForString()) + .property("merchantBaseUrl", codecForString()) + .property("merchantAuthToken", codecOptional(codecForString())) + .property("amountToSpend", codecForAmountString()) + .property("amountToWithdraw", codecForAmountString()) + .build("IntegrationTestArgs"); + +export interface AddExchangeRequest { + exchangeBaseUrl: string; +} + +export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => + buildCodecForObject<AddExchangeRequest>() + .property("exchangeBaseUrl", codecForString()) + .build("AddExchangeRequest"); + +export interface ForceExchangeUpdateRequest { + exchangeBaseUrl: string; +} + +export const codecForForceExchangeUpdateRequest = (): Codec<AddExchangeRequest> => + buildCodecForObject<AddExchangeRequest>() + .property("exchangeBaseUrl", codecForString()) + .build("AddExchangeRequest"); + +export interface GetExchangeTosRequest { + exchangeBaseUrl: string; +} + +export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> => + buildCodecForObject<GetExchangeTosRequest>() + .property("exchangeBaseUrl", codecForString()) + .build("GetExchangeTosRequest"); + +export interface AcceptManualWithdrawalRequest { + exchangeBaseUrl: string; + amount: string; +} + +export const codecForAcceptManualWithdrawalRequet = (): Codec<AcceptManualWithdrawalRequest> => + buildCodecForObject<AcceptManualWithdrawalRequest>() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForString()) + .build("AcceptManualWithdrawalRequest"); + +export interface GetWithdrawalDetailsForAmountRequest { + exchangeBaseUrl: string; + amount: string; +} + +export interface AcceptBankIntegratedWithdrawalRequest { + talerWithdrawUri: string; + exchangeBaseUrl: string; +} + +export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec<AcceptBankIntegratedWithdrawalRequest> => + buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>() + .property("exchangeBaseUrl", codecForString()) + .property("talerWithdrawUri", codecForString()) + .build("AcceptBankIntegratedWithdrawalRequest"); + +export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec<GetWithdrawalDetailsForAmountRequest> => + buildCodecForObject<GetWithdrawalDetailsForAmountRequest>() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForString()) + .build("GetWithdrawalDetailsForAmountRequest"); + +export interface AcceptExchangeTosRequest { + exchangeBaseUrl: string; + etag: string; +} + +export const codecForAcceptExchangeTosRequest = (): Codec<AcceptExchangeTosRequest> => + buildCodecForObject<AcceptExchangeTosRequest>() + .property("exchangeBaseUrl", codecForString()) + .property("etag", codecForString()) + .build("AcceptExchangeTosRequest"); + +export interface ApplyRefundRequest { + talerRefundUri: string; +} + +export const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> => + buildCodecForObject<ApplyRefundRequest>() + .property("talerRefundUri", codecForString()) + .build("ApplyRefundRequest"); + +export interface GetWithdrawalDetailsForUriRequest { + talerWithdrawUri: string; +} + +export const codecForGetWithdrawalDetailsForUri = (): Codec<GetWithdrawalDetailsForUriRequest> => + buildCodecForObject<GetWithdrawalDetailsForUriRequest>() + .property("talerWithdrawUri", codecForString()) + .build("GetWithdrawalDetailsForUriRequest"); + +export interface AbortProposalRequest { + proposalId: string; +} + +export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> => + buildCodecForObject<AbortProposalRequest>() + .property("proposalId", codecForString()) + .build("AbortProposalRequest"); + +export interface PreparePayRequest { + talerPayUri: string; +} + +export const codecForPreparePayRequest = (): Codec<PreparePayRequest> => + buildCodecForObject<PreparePayRequest>() + .property("talerPayUri", codecForString()) + .build("PreparePay"); + +export interface ConfirmPayRequest { + proposalId: string; + sessionId?: string; +} + +export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> => + buildCodecForObject<ConfirmPayRequest>() + .property("proposalId", codecForString()) + .property("sessionId", codecOptional(codecForString())) + .build("ConfirmPay"); + +export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; + +export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification; + +export interface CoreApiNotification { + type: "notification"; + payload: unknown; +} + +export interface CoreApiResponseSuccess { + // To distinguish the message from notifications + type: "response"; + operation: string; + id: string; + result: unknown; +} + +export interface CoreApiResponseError { + // To distinguish the message from notifications + type: "error"; + operation: string; + id: string; + error: TalerErrorDetails; +} + +export interface WithdrawTestBalanceRequest { + amount: string; + bankBaseUrl: string; + exchangeBaseUrl: string; +} + +export const withdrawTestBalanceDefaults = { + amount: "TESTKUDOS:10", + bankBaseUrl: "https://bank.test.taler.net/", + exchangeBaseUrl: "https://exchange.test.taler.net/", +}; + +/** + * Request to the crypto worker to make a sync signature. + */ +export interface MakeSyncSignatureRequest { + accountPriv: string; + oldHash: string | undefined; + newHash: string; +} + +/** + * Strategy for loading recovery information. + */ +export enum RecoveryMergeStrategy { + /** + * Keep the local wallet root key, import and take over providers. + */ + Ours = "ours", + + /** + * Migrate to the wallet root key from the recovery information. + */ + Theirs = "theirs", +} + +/** + * Load recovery information into the wallet. + */ +export interface RecoveryLoadRequest { + recovery: BackupRecovery; + strategy?: RecoveryMergeStrategy; +} + +export const codecForWithdrawTestBalance = (): Codec<WithdrawTestBalanceRequest> => + buildCodecForObject<WithdrawTestBalanceRequest>() + .property("amount", codecForString()) + .property("bankBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForString()) + .build("WithdrawTestBalanceRequest"); + +export interface ApplyRefundResponse { + contractTermsHash: string; + + proposalId: string; + + amountEffectivePaid: AmountString; + + amountRefundGranted: AmountString; + + amountRefundGone: AmountString; + + pendingAtExchange: boolean; + + info: OrderShortInfo; +} + +export const codecForApplyRefundResponse = (): Codec<ApplyRefundResponse> => + buildCodecForObject<ApplyRefundResponse>() + .property("amountEffectivePaid", codecForAmountString()) + .property("amountRefundGone", codecForAmountString()) + .property("amountRefundGranted", codecForAmountString()) + .property("contractTermsHash", codecForString()) + .property("pendingAtExchange", codecForBoolean()) + .property("proposalId", codecForString()) + .property("info", codecForOrderShortInfo()) + .build("ApplyRefundResponse"); + +export interface SetCoinSuspendedRequest { + coinPub: string; + suspended: boolean; +} + +export const codecForSetCoinSuspendedRequest = (): Codec<SetCoinSuspendedRequest> => + buildCodecForObject<SetCoinSuspendedRequest>() + .property("coinPub", codecForString()) + .property("suspended", codecForBoolean()) + .build("SetCoinSuspendedRequest"); + +export interface ForceRefreshRequest { + coinPubList: string[]; +} + +export const codecForForceRefreshRequest = (): Codec<ForceRefreshRequest> => + buildCodecForObject<ForceRefreshRequest>() + .property("coinPubList", codecForList(codecForString())) + .build("ForceRefreshRequest"); + +export interface PrepareTipRequest { + talerTipUri: string; +} + +export const codecForPrepareTipRequest = (): Codec<PrepareTipRequest> => + buildCodecForObject<PrepareTipRequest>() + .property("talerTipUri", codecForString()) + .build("PrepareTipRequest"); + +export interface AcceptTipRequest { + walletTipId: string; +} + +export const codecForAcceptTipRequest = (): Codec<AcceptTipRequest> => + buildCodecForObject<AcceptTipRequest>() + .property("walletTipId", codecForString()) + .build("AcceptTipRequest"); + +export interface AbortPayWithRefundRequest { + proposalId: string; +} + +export const codecForAbortPayWithRefundRequest = (): Codec<AbortPayWithRefundRequest> => + buildCodecForObject<AbortPayWithRefundRequest>() + .property("proposalId", codecForString()) + .build("AbortPayWithRefundRequest"); + +export interface CreateDepositGroupRequest { + depositPaytoUri: string; + amount: string; +} + +export const codecForCreateDepositGroupRequest = (): Codec<CreateDepositGroupRequest> => + buildCodecForObject<CreateDepositGroupRequest>() + .property("amount", codecForAmountString()) + .property("depositPaytoUri", codecForString()) + .build("CreateDepositGroupRequest"); + +export interface CreateDepositGroupResponse { + depositGroupId: string; +} + +export interface TrackDepositGroupRequest { + depositGroupId: string; +} + +export interface TrackDepositGroupResponse { + responses: { + status: number; + body: any; + }[]; +} + +export const codecForTrackDepositGroupRequest = (): Codec<TrackDepositGroupRequest> => + buildCodecForObject<TrackDepositGroupRequest>() + .property("depositGroupId", codecForAmountString()) + .build("TrackDepositGroupRequest"); + +export interface WithdrawUriInfoResponse { + amount: AmountString; + defaultExchangeBaseUrl?: string; + possibleExchanges: ExchangeListItem[]; +} + +export const codecForWithdrawUriInfoResponse = (): Codec<WithdrawUriInfoResponse> => + buildCodecForObject<WithdrawUriInfoResponse>() + .property("amount", codecForAmountString()) + .property("defaultExchangeBaseUrl", codecOptional(codecForString())) + .property("possibleExchanges", codecForList(codecForExchangeListItem())) + .build("WithdrawUriInfoResponse"); diff --git a/packages/taler-util/tsconfig.json b/packages/taler-util/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "composite": true, + "declaration": true, + "declarationMap": false, + "target": "ES6", + "module": "ESNext", + "moduleResolution": "node", + "sourceMap": true, + "lib": ["es6"], + "types": ["node"], + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "strict": true, + "strictPropertyInitialization": false, + "outDir": "lib", + "noImplicitAny": true, + "noImplicitThis": true, + "incremental": true, + "esModuleInterop": true, + "importHelpers": true, + "rootDir": "./src", + "typeRoots": ["./node_modules/@types"] + }, + "include": ["src/**/*"] +} diff --git a/packages/taler-wallet-cli/package.json b/packages/taler-wallet-cli/package.json @@ -44,6 +44,7 @@ "typescript": "^4.1.3" }, "dependencies": { + "@gnu-taler/taler-util": "workspace:*", "@gnu-taler/taler-wallet-core": "workspace:*", "@types/minimatch": "^3.0.3", "axios": "^0.21.1", diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts @@ -14,34 +14,16 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +/** + * Imports. + */ import os from "os"; import fs from "fs"; -import { - getDefaultNodeWallet, - Logger, - Amounts, - Wallet, - OperationFailedAndReportedError, - OperationFailedError, - NodeHttpLib, - PreparePayResultType, - setDangerousTimetravel, - classifyTalerUri, - TalerUriType, - decodeCrock, - addPaytoQueryParams, - codecForList, - codecForString, - printTestVectors, - NodeThreadCryptoWorkerFactory, - CryptoApi, - rsaBlind, - RecoveryMergeStrategy, - stringToBytes, -} from "@gnu-taler/taler-wallet-core"; -import * as clk from "./clk"; +import * as clk from "./clk.js"; import { deepStrictEqual } from "assert"; -import { getTestInfo, runTests } from "./integrationtests/testrunner"; +import { getTestInfo, runTests } from "./integrationtests/testrunner.js"; +import { PreparePayResultType, setDangerousTimetravel, classifyTalerUri, TalerUriType, RecoveryMergeStrategy, Amounts, addPaytoQueryParams, codecForList, codecForString } from "@gnu-taler/taler-util"; +import { Logger, Wallet, NodeHttpLib, getDefaultNodeWallet, OperationFailedAndReportedError, OperationFailedError, decodeCrock, rsaBlind, NodeThreadCryptoWorkerFactory, CryptoApi } from "@gnu-taler/taler-wallet-core"; // This module also serves as the entry point for the crypto // thread worker, and thus must expose these two handlers. @@ -811,9 +793,6 @@ testCli }); }); -testCli.subcommand("vectors", "vectors").action(async (args) => { - printTestVectors(); -}); async function read(stream: NodeJS.ReadStream) { const chunks = []; diff --git a/packages/taler-wallet-cli/src/integrationtests/harness.ts b/packages/taler-wallet-cli/src/integrationtests/harness.ts @@ -30,81 +30,83 @@ import * as path from "path"; import * as http from "http"; import { deepStrictEqual } from "assert"; import { ChildProcess, spawn } from "child_process"; +import { URL } from "url"; +import axios, { AxiosError } from "axios"; +import { + codecForMerchantOrderPrivateStatusResponse, + codecForPostOrderResponse, + PostOrderRequest, + PostOrderResponse, + MerchantOrderPrivateStatusResponse, + TippingReserveStatus, + TipCreateConfirmation, + TipCreateRequest, + MerchantInstancesResponse, +} from "./merchantApiTypes"; +import { + createEddsaKeyPair, + eddsaGetPublic, + EddsaKeyPair, + encodeCrock, + getRandomBytes, + openPromise, + OperationFailedError, +} from "@gnu-taler/taler-wallet-core"; import { - Configuration, AmountJson, Amounts, + Configuration, + AmountString, Codec, buildCodecForObject, codecForString, Duration, + parsePaytoUri, CoreApiResponse, - PreparePayResult, + ApplyRefundRequest, + ApplyRefundResponse, + codecForApplyRefundResponse, PreparePayRequest, + PreparePayResult, codecForPreparePayResult, - OperationFailedError, - AddExchangeRequest, - ExchangesListRespose, - codecForExchangesListResponse, - GetWithdrawalDetailsForUriRequest, - WithdrawUriInfoResponse, - codecForWithdrawUriInfoResponse, + CreateDepositGroupRequest, + CreateDepositGroupResponse, + AbortPayWithRefundRequest, ConfirmPayRequest, ConfirmPayResult, codecForConfirmPayResult, - IntegrationTestArgs, - TestPayArgs, + PrepareTipRequest, + PrepareTipResult, + codecForPrepareTipResult, + AcceptTipRequest, + CoinDumpJson, + codecForAny, + AddExchangeRequest, + ForceExchangeUpdateRequest, + ForceRefreshRequest, + ExchangesListRespose, + codecForExchangesListResponse, BalancesResponse, codecForBalancesResponse, - encodeCrock, - getRandomBytes, - EddsaKeyPair, - eddsaGetPublic, - createEddsaKeyPair, TransactionsResponse, codecForTransactionsResponse, - WithdrawTestBalanceRequest, - AmountString, - ApplyRefundRequest, - codecForApplyRefundResponse, - codecForAny, - CoinDumpJson, - ForceExchangeUpdateRequest, - ForceRefreshRequest, - PrepareTipResult, - PrepareTipRequest, - codecForPrepareTipResult, - AcceptTipRequest, - AbortPayWithRefundRequest, - openPromise, - parsePaytoUri, - CreateDepositGroupRequest, - CreateDepositGroupResponse, TrackDepositGroupRequest, TrackDepositGroupResponse, + IntegrationTestArgs, + TestPayArgs, + WithdrawTestBalanceRequest, + GetWithdrawalDetailsForUriRequest, + WithdrawUriInfoResponse, + codecForWithdrawUriInfoResponse, + BackupRecovery, RecoveryLoadRequest, -} from "@gnu-taler/taler-wallet-core"; -import { URL } from "url"; -import axios, { AxiosError } from "axios"; -import { - codecForMerchantOrderPrivateStatusResponse, - codecForPostOrderResponse, - PostOrderRequest, - PostOrderResponse, - MerchantOrderPrivateStatusResponse, - TippingReserveStatus, - TipCreateConfirmation, - TipCreateRequest, - MerchantInstancesResponse, -} from "./merchantApiTypes"; -import { ApplyRefundResponse } from "@gnu-taler/taler-wallet-core"; -import { PendingOperationsResponse } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig } from "./denomStructures"; +} from "@gnu-taler/taler-util"; import { AddBackupProviderRequest, BackupInfo, - BackupRecovery, } from "@gnu-taler/taler-wallet-core/src/operations/backup"; +import { PendingOperationsResponse } from "@gnu-taler/taler-wallet-core/src/pending-types"; +import { CoinConfig } from "./denomStructures.js"; const exec = util.promisify(require("child_process").exec); @@ -486,7 +488,7 @@ export async function pingProc( } } -export interface ExchangeBankAccount { +export interface HarnessExchangeBankAccount { accountName: string; accountPassword: string; accountPaytoUri: string; @@ -573,7 +575,7 @@ export namespace BankApi { export async function adminAddIncoming( bank: BankServiceInterface, params: { - exchangeBankAccount: ExchangeBankAccount; + exchangeBankAccount: HarnessExchangeBankAccount; amount: string; reservePub: string; debitAccountPayto: string; @@ -701,7 +703,7 @@ export class BankService implements BankServiceInterface { async createExchangeAccount( accountName: string, password: string, - ): Promise<ExchangeBankAccount> { + ): Promise<HarnessExchangeBankAccount> { await sh( this.globalTestState, "taler-bank-manage_django", @@ -944,7 +946,7 @@ export class ExchangeService implements ExchangeServiceInterface { async addBankAccount( localName: string, - exchangeBankAccount: ExchangeBankAccount, + exchangeBankAccount: HarnessExchangeBankAccount, ): Promise<void> { const config = Configuration.load(this.configFilename); config.setString( diff --git a/packages/taler-wallet-cli/src/integrationtests/helpers.ts b/packages/taler-wallet-cli/src/integrationtests/helpers.ts @@ -24,38 +24,38 @@ * Imports */ import { - GlobalTestState, + FaultInjectedExchangeService, + FaultInjectedMerchantService, +} from "./faultInjection"; +import { CoinConfig, defaultCoinConfig } from "./denomStructures"; +import { + AmountString, + Duration, + ContractTerms, + PreparePayResultType, + ConfirmPayResultType, +} from "@gnu-taler/taler-util"; +import { DbInfo, + BankService, ExchangeService, - WalletCli, MerchantService, + WalletCli, + GlobalTestState, setupDb, - BankService, - ExchangeBankAccount, - MerchantServiceInterface, + ExchangeServiceInterface, BankApi, BankAccessApi, + MerchantServiceInterface, MerchantPrivateApi, - ExchangeServiceInterface, -} from "./harness"; -import { - AmountString, - Duration, - PreparePayResultType, - ConfirmPayResultType, - ContractTerms, -} from "@gnu-taler/taler-wallet-core"; -import { - FaultInjectedExchangeService, - FaultInjectedMerchantService, -} from "./faultInjection"; -import { CoinConfig, defaultCoinConfig } from "./denomStructures"; + HarnessExchangeBankAccount, +} from "./harness.js"; export interface SimpleTestEnvironment { commonDb: DbInfo; bank: BankService; exchange: ExchangeService; - exchangeBankAccount: ExchangeBankAccount; + exchangeBankAccount: HarnessExchangeBankAccount; merchant: MerchantService; wallet: WalletCli; } @@ -154,7 +154,7 @@ export interface FaultyMerchantTestEnvironment { bank: BankService; exchange: ExchangeService; faultyExchange: FaultInjectedExchangeService; - exchangeBankAccount: ExchangeBankAccount; + exchangeBankAccount: HarnessExchangeBankAccount; merchant: MerchantService; faultyMerchant: FaultInjectedMerchantService; wallet: WalletCli; diff --git a/packages/taler-wallet-cli/src/integrationtests/merchantApiTypes.ts b/packages/taler-wallet-cli/src/integrationtests/merchantApiTypes.ts @@ -41,7 +41,7 @@ import { Timestamp, CoinPublicKeyString, EddsaPublicKeyString, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; import { codecForAmountString } from "@gnu-taler/taler-wallet-core/lib/util/amounts"; export interface PostOrderRequest { diff --git a/packages/taler-wallet-cli/src/integrationtests/sync.ts b/packages/taler-wallet-cli/src/integrationtests/sync.ts @@ -18,7 +18,7 @@ * Imports. */ import axios from "axios"; -import { Configuration, URL } from "@gnu-taler/taler-wallet-core"; +import { URL } from "@gnu-taler/taler-wallet-core"; import * as fs from "fs"; import * as util from "util"; import { @@ -30,6 +30,7 @@ import { setupDb, sh, } from "./harness"; +import { Configuration } from "@gnu-taler/taler-util"; const exec = util.promisify(require("child_process").exec); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts @@ -28,11 +28,9 @@ import { BankAccessApi, } from "./harness"; import { - PreparePayResultType, - ExchangesListRespose, URL, - TalerErrorCode, } from "@gnu-taler/taler-wallet-core"; +import { ExchangesListRespose, TalerErrorCode } from "@gnu-taler/taler-util"; import { FaultInjectedExchangeService, FaultInjectionResponseContext, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts @@ -17,11 +17,11 @@ /** * Imports. */ -import { CoreApiResponse } from "@gnu-taler/taler-wallet-core"; +import { CoreApiResponse } from "@gnu-taler/taler-util"; import { CoinConfig, defaultCoinConfig } from "./denomStructures"; import { DbInfo, - ExchangeBankAccount, + HarnessExchangeBankAccount, ExchangeService, GlobalTestState, MerchantService, @@ -44,7 +44,7 @@ const merchantIban = "DE42500105171245624648"; export interface LibeufinTestEnvironment { commonDb: DbInfo; exchange: ExchangeService; - exchangeBankAccount: ExchangeBankAccount; + exchangeBankAccount: HarnessExchangeBankAccount; merchant: MerchantService; wallet: WalletCli; libeufinSandbox: LibeufinSandboxService; @@ -181,7 +181,7 @@ export async function createLibeufinTestEnvironment( database: db.connStr, }); - const exchangeBankAccount: ExchangeBankAccount = { + const exchangeBankAccount: HarnessExchangeBankAccount = { accountName: "twguser", accountPassword: "twgpw", accountPaytoUri: `payto://iban/${exchangeIban}?receiver-name=Exchange`, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-refund.ts @@ -17,15 +17,11 @@ /** * Imports. */ -import { CoreApiResponse } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "./denomStructures"; import { GlobalTestState } from "./harness"; -import { getRandomIban } from "./helpers"; import { SandboxUserBundle, NexusUserBundle, launchLibeufinServices, - LibeufinNexusApi, LibeufinSandboxApi, } from "./libeufin"; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-tutorial.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-tutorial.ts @@ -17,13 +17,9 @@ /** * Imports. */ -import { CoreApiResponse } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "./denomStructures"; import { GlobalTestState } from "./harness"; import { - LibeufinNexusApi, LibeufinNexusService, - LibeufinSandboxApi, LibeufinSandboxService, LibeufinCli, } from "./libeufin"; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-exchange-confusion.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-exchange-confusion.ts @@ -35,8 +35,7 @@ import { PreparePayResultType, codecForMerchantOrderStatusUnpaid, ConfirmPayResultType, - URL, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; import axios from "axios"; import { FaultInjectedExchangeService, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-longpolling.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-longpolling.ts @@ -23,9 +23,9 @@ import { PreparePayResultType, codecForMerchantOrderStatusUnpaid, ConfirmPayResultType, - URL, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; import axios from "axios"; +import { URL } from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts @@ -33,9 +33,9 @@ import { import { durationFromSpec, PreparePayResultType, - URL, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; import axios from "axios"; +import { URL } from "@gnu-taler/taler-wallet-core"; async function testRefundApiWithFulfillmentUrl( t: GlobalTestState, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts b/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts @@ -22,9 +22,8 @@ /** * Imports. */ +import { PreparePayResultType, TalerErrorCode } from "@gnu-taler/taler-util"; import { - PreparePayResultType, - TalerErrorCode, URL, } from "@gnu-taler/taler-wallet-core"; import { diff --git a/packages/taler-wallet-cli/src/integrationtests/test-pay-paid.ts b/packages/taler-wallet-cli/src/integrationtests/test-pay-paid.ts @@ -26,10 +26,10 @@ import { PreparePayResultType, codecForMerchantOrderStatusUnpaid, ConfirmPayResultType, - URL, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; import axios from "axios"; import { FaultInjectionRequestContext } from "./faultInjection"; +import { URL } from "@gnu-taler/taler-wallet-core"; /** * Run test for the wallets repurchase detection mechanism diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts @@ -21,8 +21,8 @@ import { GlobalTestState, MerchantPrivateApi, WalletCli } from "./harness"; import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; import { PreparePayResultType, - TalerErrorCode, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; +import { TalerErrorCode } from "@gnu-taler/taler-util"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts @@ -37,7 +37,7 @@ import { FaultInjectionRequestContext, FaultInjectionResponseContext, } from "./faultInjection"; -import { CoreApiResponse } from "@gnu-taler/taler-wallet-core"; +import { CoreApiResponse } from "@gnu-taler/taler-util"; import { defaultCoinConfig } from "./denomStructures"; /** diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-idempotency.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-idempotency.ts @@ -19,7 +19,7 @@ */ import { GlobalTestState, MerchantPrivateApi } from "./harness"; import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; -import { PreparePayResultType } from "@gnu-taler/taler-wallet-core"; +import { PreparePayResultType } from "@gnu-taler/taler-util"; /** * Test the wallet-core payment API, especially that repeated operations diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts @@ -22,20 +22,13 @@ import { withdrawViaBank, createFaultInjectedMerchantTestkudosEnvironment, } from "./helpers"; -import { - PreparePayResultType, - codecForMerchantOrderStatusUnpaid, - ConfirmPayResultType, - URL, - codecForExchangeKeysJson, - TalerErrorDetails, - TalerErrorCode, -} from "@gnu-taler/taler-wallet-core"; import axios from "axios"; import { FaultInjectionRequestContext, FaultInjectionResponseContext, } from "./faultInjection"; +import { codecForMerchantOrderStatusUnpaid, ConfirmPayResultType, PreparePayResultType, TalerErrorCode, TalerErrorDetails } from "@gnu-taler/taler-util"; +import { URL } from "@gnu-taler/taler-wallet-core"; /** * Run test for a payment where the merchant has a transient diff --git a/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts b/packages/taler-wallet-cli/src/integrationtests/test-paywall-flow.ts @@ -23,7 +23,7 @@ import { PreparePayResultType, codecForMerchantOrderStatusUnpaid, ConfirmPayResultType, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; import axios from "axios"; /** diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts b/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts @@ -19,7 +19,7 @@ */ import { GlobalTestState, MerchantPrivateApi } from "./harness"; import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; -import { durationFromSpec } from "@gnu-taler/taler-wallet-core"; +import { durationFromSpec } from "@gnu-taler/taler-util"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts b/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts @@ -28,7 +28,7 @@ import { timestampAddDuration, getTimestampNow, timestampTruncateToSecond, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts b/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts @@ -23,7 +23,7 @@ import { TransactionType, Amounts, durationFromSpec, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund.ts b/packages/taler-wallet-cli/src/integrationtests/test-refund.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { durationFromSpec } from "@gnu-taler/taler-wallet-core"; +import { durationFromSpec } from "@gnu-taler/taler-util"; import { GlobalTestState, MerchantPrivateApi } from "./harness"; import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-timetravel-autorefresh.ts b/packages/taler-wallet-cli/src/integrationtests/test-timetravel-autorefresh.ts @@ -21,9 +21,9 @@ import { ConfirmPayResultType, Duration, durationFromSpec, - PendingOperationsResponse, PreparePayResultType, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; +import { PendingOperationsResponse } from "@gnu-taler/taler-wallet-core"; import { makeNoFeeCoinConfig } from "./denomStructures"; import { BankService, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-timetravel-withdraw.ts b/packages/taler-wallet-cli/src/integrationtests/test-timetravel-withdraw.ts @@ -23,7 +23,7 @@ import { withdrawViaBank, startWithdrawViaBank, } from "./helpers"; -import { Duration, TransactionType } from "@gnu-taler/taler-wallet-core"; +import { Duration, TransactionType } from "@gnu-taler/taler-util"; /** * Basic time travel test. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts b/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts @@ -17,12 +17,9 @@ /** * Imports. */ -import { PreparePayResultType } from "@gnu-taler/taler-wallet-core"; -import { testPay } from "@gnu-taler/taler-wallet-core/src/operations/testing"; +import { PreparePayResultType } from "@gnu-taler/taler-util"; import { GlobalTestState, - BankApi, - BankAccessApi, WalletCli, MerchantPrivateApi, } from "./harness"; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts @@ -17,12 +17,9 @@ /** * Imports. */ +import { TalerErrorCode } from "@gnu-taler/taler-util"; import { GlobalTestState, BankApi, BankAccessApi } from "./harness"; import { createSimpleTestkudosEnvironment } from "./helpers"; -import { - codecForBalancesResponse, - TalerErrorCode, -} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts @@ -19,7 +19,7 @@ */ import { GlobalTestState, BankApi, BankAccessApi } from "./harness"; import { createSimpleTestkudosEnvironment } from "./helpers"; -import { codecForBalancesResponse } from "@gnu-taler/taler-wallet-core"; +import { codecForBalancesResponse } from "@gnu-taler/taler-util"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts @@ -19,8 +19,8 @@ */ import { GlobalTestState, BankApi } from "./harness"; import { createSimpleTestkudosEnvironment } from "./helpers"; -import { CoreApiResponse } from "@gnu-taler/taler-wallet-core"; -import { codecForBalancesResponse } from "@gnu-taler/taler-wallet-core"; +import { CoreApiResponse } from "@gnu-taler/taler-util"; +import { codecForBalancesResponse } from "@gnu-taler/taler-util"; /** * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-wallet-cli/tsconfig.json b/packages/taler-wallet-cli/tsconfig.json @@ -25,6 +25,9 @@ "references": [ { "path": "../taler-wallet-core/" + }, + { + "path": "../taler-util/" } ] } diff --git a/packages/taler-wallet-core/package.json b/packages/taler-wallet-core/package.json @@ -58,6 +58,7 @@ }, "dependencies": { "@gnu-taler/idb-bridge": "workspace:*", + "@gnu-taler/taler-util": "workspace:*", "@types/node": "^14.14.22", "axios": "^0.21.1", "big-integer": "^1.6.48", diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts @@ -0,0 +1,141 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Types used by the wallet crypto worker. + * + * These types are defined in a separate file make tree shaking easier, since + * some components use these types (via RPC) but do not depend on the wallet + * code directly. + * + * @author Florian Dold <dold@taler.net> + */ + +/** + * Imports. + */ +import { AmountJson } from "@gnu-taler/taler-util"; + +export interface RefreshNewDenomInfo { + count: number; + value: AmountJson; + feeWithdraw: AmountJson; + denomPub: string; +} + +/** + * Request to derive a refresh session from the refresh session + * secret seed. + */ +export interface DeriveRefreshSessionRequest { + sessionSecretSeed: string; + kappa: number; + meltCoinPub: string; + meltCoinPriv: string; + meltCoinDenomPubHash: string; + newCoinDenoms: RefreshNewDenomInfo[]; + feeRefresh: AmountJson; +} + +/** + * + */ +export interface DerivedRefreshSession { + /** + * Public key that's being melted in this session. + */ + meltCoinPub: string; + + /** + * Signature to confirm the melting. + */ + confirmSig: string; + + /** + * Planchets for each cut-and-choose instance. + */ + planchetsForGammas: { + /** + * Public key for the coin. + */ + publicKey: string; + + /** + * Private key for the coin. + */ + privateKey: string; + + /** + * Blinded public key. + */ + coinEv: string; + + /** + * Hash of the blinded public key. + */ + coinEvHash: string; + + /** + * Blinding key used. + */ + blindingKey: string; + }[][]; + + /** + * The transfer keys, kappa of them. + */ + transferPubs: string[]; + + /** + * Private keys for the transfer public keys. + */ + transferPrivs: string[]; + + /** + * Hash of the session. + */ + hash: string; + + /** + * Exact value that is being melted. + */ + meltValueWithFee: AmountJson; +} + +export interface DeriveTipRequest { + secretSeed: string; + denomPub: string; + planchetIndex: number; +} + +/** + * Tipping planchet stored in the database. + */ +export interface DerivedTipPlanchet { + blindingKey: string; + coinEv: string; + coinEvHash: string; + coinPriv: string; + coinPub: string; +} + +export interface SignTrackTransactionRequest { + contractTermsHash: string; + wireHash: string; + coinPub: string; + merchantPriv: string; + merchantPub: string; +} diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -22,11 +22,11 @@ /** * Imports. */ -import { CoinRecord, DenominationRecord, WireFee } from "../../types/dbTypes"; +import { CoinRecord, DenominationRecord, WireFee } from "../../db"; import { CryptoWorker } from "./cryptoWorker"; -import { RecoupRequest, CoinDepositPermission } from "../../types/talerTypes"; +import { RecoupRequest, CoinDepositPermission } from "@gnu-taler/taler-util"; import { BenchmarkResult, @@ -34,7 +34,7 @@ import { PlanchetCreationRequest, DepositInfo, MakeSyncSignatureRequest, -} from "../../types/walletTypes"; +} from "@gnu-taler/taler-util"; import * as timer from "../../util/timer"; import { Logger } from "../../util/logging"; @@ -44,7 +44,7 @@ import { DeriveRefreshSessionRequest, DeriveTipRequest, SignTrackTransactionRequest, -} from "../../types/cryptoTypes"; +} from "../cryptoTypes.js"; const logger = new Logger("cryptoApi.ts"); diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -26,23 +26,25 @@ * Imports. */ +// FIXME: Crypto should not use DB Types! import { CoinRecord, DenominationRecord, RefreshPlanchet, WireFee, CoinSourceType, -} from "../../types/dbTypes"; +} from "../../db.js"; -import { CoinDepositPermission, RecoupRequest } from "../../types/talerTypes"; +import { CoinDepositPermission, RecoupRequest } from "@gnu-taler/taler-util"; +// FIXME: These types should be internal to the wallet! import { BenchmarkResult, PlanchetCreationResult, PlanchetCreationRequest, DepositInfo, MakeSyncSignatureRequest, -} from "../../types/walletTypes"; -import { AmountJson, Amounts } from "../../util/amounts"; +} from "@gnu-taler/taler-util"; +import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import * as timer from "../../util/timer"; import { encodeCrock, @@ -64,7 +66,7 @@ import { } from "../talerCrypto"; import { randomBytes } from "../primitives/nacl-fast"; import { kdf } from "../primitives/kdf"; -import { Timestamp, timestampTruncateToSecond } from "../../util/time"; +import { Timestamp, timestampTruncateToSecond } from "@gnu-taler/taler-util"; import { Logger } from "../../util/logging"; import { @@ -73,7 +75,7 @@ import { DeriveRefreshSessionRequest, DeriveTipRequest, SignTrackTransactionRequest, -} from "../../types/cryptoTypes"; +} from "../cryptoTypes.js"; const logger = new Logger("cryptoImplementation.ts"); diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts @@ -1,4 +1,3 @@ -import { MetaStores, Stores } from "./types/dbTypes"; import { openDatabase, Database, @@ -11,8 +10,12 @@ import { IDBDatabase, IDBObjectStore, IDBTransaction, + IDBKeyPath, } from "@gnu-taler/idb-bridge"; import { Logger } from "./util/logging"; +import { AmountJson, AmountString, Auditor, CoinDepositPermission, ContractTerms, Duration, ExchangeSignKeyJson, InternationalizedString, MerchantInfo, Product, RefreshReason, ReserveTransaction, TalerErrorDetails, Timestamp } from "@gnu-taler/taler-util"; +import { RetryInfo } from "./util/retries.js"; +import { PayCoinSelection } from "./util/coinSelection.js"; /** * Name of the Taler database. This is effectively the major @@ -166,3 +169,1735 @@ export async function openTalerDatabase( export function deleteTalerDatabase(idbFactory: IDBFactory): void { Database.deleteDatabase(idbFactory, TALER_DB_NAME); } + + +export enum ReserveRecordStatus { + /** + * Reserve must be registered with the bank. + */ + REGISTERING_BANK = "registering-bank", + + /** + * We've registered reserve's information with the bank + * and are now waiting for the user to confirm the withdraw + * with the bank (typically 2nd factor auth). + */ + WAIT_CONFIRM_BANK = "wait-confirm-bank", + + /** + * Querying reserve status with the exchange. + */ + QUERYING_STATUS = "querying-status", + + /** + * The corresponding withdraw record has been created. + * No further processing is done, unless explicitly requested + * by the user. + */ + DORMANT = "dormant", + + /** + * The bank aborted the withdrawal. + */ + BANK_ABORTED = "bank-aborted", +} + +export interface ReserveBankInfo { + /** + * Status URL that the wallet will use to query the status + * of the Taler withdrawal operation on the bank's side. + */ + statusUrl: string; + + confirmUrl?: string; + + /** + * Exchange payto URI that the bank will use to fund the reserve. + */ + exchangePaytoUri: string; +} + +/** + * A reserve record as stored in the wallet's database. + */ +export interface ReserveRecord { + /** + * The reserve public key. + */ + reservePub: string; + + /** + * The reserve private key. + */ + reservePriv: string; + + /** + * The exchange base URL. + */ + exchangeBaseUrl: string; + + /** + * Currency of the reserve. + */ + currency: string; + + /** + * Time when the reserve was created. + */ + timestampCreated: Timestamp; + + /** + * Time when the information about this reserve was posted to the bank. + * + * Only applies if bankWithdrawStatusUrl is defined. + * + * Set to 0 if that hasn't happened yet. + */ + timestampReserveInfoPosted: Timestamp | undefined; + + /** + * Time when the reserve was confirmed by the bank. + * + * Set to undefined if not confirmed yet. + */ + timestampBankConfirmed: Timestamp | undefined; + + /** + * Wire information (as payto URI) for the bank account that + * transfered funds for this reserve. + */ + senderWire?: string; + + /** + * Amount that was sent by the user to fund the reserve. + */ + instructedAmount: AmountJson; + + /** + * Extra state for when this is a withdrawal involving + * a Taler-integrated bank. + */ + bankInfo?: ReserveBankInfo; + + initialWithdrawalGroupId: string; + + /** + * Did we start the first withdrawal for this reserve? + * + * We only report a pending withdrawal for the reserve before + * the first withdrawal has started. + */ + initialWithdrawalStarted: boolean; + + /** + * Initial denomination selection, stored here so that + * we can show this information in the transactions/balances + * before we have a withdrawal group. + */ + initialDenomSel: DenomSelectionState; + + reserveStatus: ReserveRecordStatus; + + /** + * Was a reserve query requested? If so, query again instead + * of going into dormant status. + */ + requestedQuery: boolean; + + /** + * Time of the last successful status query. + */ + lastSuccessfulStatusQuery: Timestamp | undefined; + + /** + * Retry info. This field is present even if no retry is scheduled, + * because we need it to be present for the index on the object store + * to work. + */ + retryInfo: RetryInfo; + + /** + * Last error that happened in a reserve operation + * (either talking to the bank or the exchange). + */ + lastError: TalerErrorDetails | undefined; +} + +/** + * Auditor record as stored with currencies in the exchange database. + */ +export interface AuditorRecord { + /** + * Base url of the auditor. + */ + baseUrl: string; + + /** + * Public signing key of the auditor. + */ + auditorPub: string; + + /** + * Time when the auditing expires. + */ + expirationStamp: number; +} + +/** + * Exchange for currencies as stored in the wallet's currency + * information database. + */ +export interface ExchangeForCurrencyRecord { + /** + * FIXME: unused? + */ + exchangeMasterPub: string; + + /** + * Base URL of the exchange. + */ + exchangeBaseUrl: string; +} + +/** + * Information about a currency as displayed in the wallet's database. + */ +export interface CurrencyRecord { + /** + * Name of the currency. + */ + name: string; + + /** + * Number of fractional digits to show when rendering the currency. + */ + fractionalDigits: number; + + /** + * Auditors that the wallet trusts for this currency. + */ + auditors: AuditorRecord[]; + + /** + * Exchanges that the wallet trusts for this currency. + */ + exchanges: ExchangeForCurrencyRecord[]; +} + +/** + * Status of a denomination. + */ +export enum DenominationStatus { + /** + * Verification was delayed. + */ + Unverified = "unverified", + /** + * Verified as valid. + */ + VerifiedGood = "verified-good", + /** + * Verified as invalid. + */ + VerifiedBad = "verified-bad", +} + +/** + * Denomination record as stored in the wallet's database. + */ +export interface DenominationRecord { + /** + * Value of one coin of the denomination. + */ + value: AmountJson; + + /** + * The denomination public key. + */ + denomPub: string; + + /** + * Hash of the denomination public key. + * Stored in the database for faster lookups. + */ + denomPubHash: string; + + /** + * Fee for withdrawing. + */ + feeWithdraw: AmountJson; + + /** + * Fee for depositing. + */ + feeDeposit: AmountJson; + + /** + * Fee for refreshing. + */ + feeRefresh: AmountJson; + + /** + * Fee for refunding. + */ + feeRefund: AmountJson; + + /** + * Validity start date of the denomination. + */ + stampStart: Timestamp; + + /** + * Date after which the currency can't be withdrawn anymore. + */ + stampExpireWithdraw: Timestamp; + + /** + * Date after the denomination officially doesn't exist anymore. + */ + stampExpireLegal: Timestamp; + + /** + * Data after which coins of this denomination can't be deposited anymore. + */ + stampExpireDeposit: Timestamp; + + /** + * Signature by the exchange's master key over the denomination + * information. + */ + masterSig: string; + + /** + * Did we verify the signature on the denomination? + * + * FIXME: Rename to "verificationStatus"? + */ + status: DenominationStatus; + + /** + * Was this denomination still offered by the exchange the last time + * we checked? + * Only false when the exchange redacts a previously published denomination. + */ + isOffered: boolean; + + /** + * Did the exchange revoke the denomination? + * When this field is set to true in the database, the same transaction + * should also mark all affected coins as revoked. + */ + isRevoked: boolean; + + /** + * Base URL of the exchange. + */ + exchangeBaseUrl: string; +} + +/** + * Details about the exchange that we only know after + * querying /keys and /wire. + */ +export interface ExchangeDetails { + /** + * Master public key of the exchange. + */ + masterPublicKey: string; + + /** + * Auditors (partially) auditing the exchange. + */ + auditors: Auditor[]; + + /** + * Currency that the exchange offers. + */ + currency: string; + + /** + * Last observed protocol version. + */ + protocolVersion: string; + + reserveClosingDelay: Duration; + + /** + * Signing keys we got from the exchange, can also contain + * older signing keys that are not returned by /keys anymore. + */ + signingKeys: ExchangeSignKeyJson[]; + + /** + * Timestamp for last update. + */ + lastUpdateTime: Timestamp; + + /** + * When should we next update the information about the exchange? + */ + nextUpdateTime: Timestamp; +} + +export enum ExchangeUpdateStatus { + FetchKeys = "fetch-keys", + FetchWire = "fetch-wire", + FetchTerms = "fetch-terms", + FinalizeUpdate = "finalize-update", + Finished = "finished", +} + +export interface ExchangeBankAccount { + payto_uri: string; + master_sig: string; +} + +export interface ExchangeWireInfo { + feesForType: { [wireMethod: string]: WireFee[] }; + accounts: ExchangeBankAccount[]; +} + +export enum ExchangeUpdateReason { + Initial = "initial", + Forced = "forced", + Scheduled = "scheduled", +} + +/** + * Exchange record as stored in the wallet's database. + */ +export interface ExchangeRecord { + /** + * Base url of the exchange. + */ + baseUrl: string; + + /** + * Did we finish adding the exchange? + */ + addComplete: boolean; + + /** + * Is this a permanent or temporary exchange record? + */ + permanent: boolean; + + /** + * Was the exchange added as a built-in exchange? + */ + builtIn: boolean; + + /** + * Details, once known. + */ + details: ExchangeDetails | undefined; + + /** + * Mapping from wire method type to the wire fee. + */ + wireInfo: ExchangeWireInfo | undefined; + + /** + * Terms of service text or undefined if not downloaded yet. + * + * This is just used as a cache of the last downloaded ToS. + */ + termsOfServiceText: string | undefined; + + /** + * ETag for last terms of service download. + */ + termsOfServiceLastEtag: string | undefined; + + /** + * ETag for last terms of service download. + */ + termsOfServiceAcceptedEtag: string | undefined; + + /** + * Time when the update to the exchange has been started or + * undefined if no update is in progress. + */ + updateStarted: Timestamp | undefined; + + /** + * Status of updating the info about the exchange. + */ + updateStatus: ExchangeUpdateStatus; + + updateReason?: ExchangeUpdateReason; + + lastError?: TalerErrorDetails; + + /** + * Retry status for fetching updated information about the exchange. + */ + retryInfo: RetryInfo; + + /** + * Next time that we should check if coins need to be refreshed. + * + * Updated whenever the exchange's denominations are updated or when + * the refresh check has been done. + */ + nextRefreshCheck?: Timestamp; +} + +/** + * A coin that isn't yet signed by an exchange. + */ +export interface PlanchetRecord { + /** + * Public key of the coin. + */ + coinPub: string; + + /** + * Private key of the coin. + */ + coinPriv: string; + + /** + * Withdrawal group that this planchet belongs to + * (or the empty string). + */ + withdrawalGroupId: string; + + /** + * Index within the withdrawal group (or -1). + */ + coinIdx: number; + + withdrawalDone: boolean; + + lastError: TalerErrorDetails | undefined; + + /** + * Public key of the reserve that this planchet + * is being withdrawn from. + * + * Can be the empty string (non-null/undefined for DB indexing) + * if this is a tipping reserve. + */ + reservePub: string; + + denomPubHash: string; + + denomPub: string; + + blindingKey: string; + + withdrawSig: string; + + coinEv: string; + + coinEvHash: string; + + coinValue: AmountJson; + + isFromTip: boolean; +} + +/** + * Planchet for a coin during refrehs. + */ +export interface RefreshPlanchet { + /** + * Public key for the coin. + */ + publicKey: string; + + /** + * Private key for the coin. + */ + privateKey: string; + + /** + * Blinded public key. + */ + coinEv: string; + + coinEvHash: string; + + /** + * Blinding key used. + */ + blindingKey: string; +} + +/** + * Status of a coin. + */ +export enum CoinStatus { + /** + * Withdrawn and never shown to anybody. + */ + Fresh = "fresh", + /** + * A coin that has been spent and refreshed. + */ + Dormant = "dormant", +} + +export enum CoinSourceType { + Withdraw = "withdraw", + Refresh = "refresh", + Tip = "tip", +} + +export interface WithdrawCoinSource { + type: CoinSourceType.Withdraw; + + /** + * Can be the empty string for orphaned coins. + */ + withdrawalGroupId: string; + + /** + * Index of the coin in the withdrawal session. + */ + coinIndex: number; + + /** + * Reserve public key for the reserve we got this coin from. + */ + reservePub: string; +} + +export interface RefreshCoinSource { + type: CoinSourceType.Refresh; + oldCoinPub: string; +} + +export interface TipCoinSource { + type: CoinSourceType.Tip; + walletTipId: string; + coinIndex: number; +} + +export type CoinSource = WithdrawCoinSource | RefreshCoinSource | TipCoinSource; + +/** + * CoinRecord as stored in the "coins" data store + * of the wallet database. + */ +export interface CoinRecord { + /** + * Where did the coin come from? Used for recouping coins. + */ + coinSource: CoinSource; + + /** + * Public key of the coin. + */ + coinPub: string; + + /** + * Private key to authorize operations on the coin. + */ + coinPriv: string; + + /** + * Key used by the exchange used to sign the coin. + */ + denomPub: string; + + /** + * Hash of the public key that signs the coin. + */ + denomPubHash: string; + + /** + * Unblinded signature by the exchange. + */ + denomSig: string; + + /** + * Amount that's left on the coin. + */ + currentAmount: AmountJson; + + /** + * Base URL that identifies the exchange from which we got the + * coin. + */ + exchangeBaseUrl: string; + + /** + * The coin is currently suspended, and will not be used for payments. + */ + suspended: boolean; + + /** + * Blinding key used when withdrawing the coin. + * Potentionally used again during payback. + */ + blindingKey: string; + + /** + * Hash of the coin envelope. + * + * Stored here for indexing purposes, so that when looking at a + * reserve history, we can quickly find the coin for a withdrawal transaction. + */ + coinEvHash: string; + + /** + * Status of the coin. + */ + status: CoinStatus; +} + +export enum ProposalStatus { + /** + * Not downloaded yet. + */ + DOWNLOADING = "downloading", + /** + * Proposal downloaded, but the user needs to accept/reject it. + */ + PROPOSED = "proposed", + /** + * The user has accepted the proposal. + */ + ACCEPTED = "accepted", + /** + * The user has rejected the proposal. + */ + REFUSED = "refused", + /** + * Downloading or processing the proposal has failed permanently. + */ + PERMANENTLY_FAILED = "permanently-failed", + /** + * Downloaded proposal was detected as a re-purchase. + */ + REPURCHASE = "repurchase", +} + +export interface ProposalDownload { + /** + * The contract that was offered by the merchant. + */ + contractTermsRaw: any; + + contractData: WalletContractData; +} + +/** + * Record for a downloaded order, stored in the wallet's database. + */ +export interface ProposalRecord { + orderId: string; + + merchantBaseUrl: string; + + /** + * Downloaded data from the merchant. + */ + download: ProposalDownload | undefined; + + /** + * Unique ID when the order is stored in the wallet DB. + */ + proposalId: string; + + /** + * Timestamp (in ms) of when the record + * was created. + */ + timestamp: Timestamp; + + /** + * Private key for the nonce. + */ + noncePriv: string; + + /** + * Public key for the nonce. + */ + noncePub: string; + + claimToken: string | undefined; + + proposalStatus: ProposalStatus; + + repurchaseProposalId: string | undefined; + + /** + * Session ID we got when downloading the contract. + */ + downloadSessionId?: string; + + /** + * Retry info, even present when the operation isn't active to allow indexing + * on the next retry timestamp. + */ + retryInfo: RetryInfo; + + lastError: TalerErrorDetails | undefined; +} + +/** + * Status of a tip we got from a merchant. + */ +export interface TipRecord { + lastError: TalerErrorDetails | undefined; + + /** + * Has the user accepted the tip? Only after the tip has been accepted coins + * withdrawn from the tip may be used. + */ + acceptedTimestamp: Timestamp | undefined; + + /** + * The tipped amount. + */ + tipAmountRaw: AmountJson; + + tipAmountEffective: AmountJson; + + /** + * Timestamp, the tip can't be picked up anymore after this deadline. + */ + tipExpiration: Timestamp; + + /** + * The exchange that will sign our coins, chosen by the merchant. + */ + exchangeBaseUrl: string; + + /** + * Base URL of the merchant that is giving us the tip. + */ + merchantBaseUrl: string; + + /** + * Denomination selection made by the wallet for picking up + * this tip. + */ + denomsSel: DenomSelectionState; + + /** + * Tip ID chosen by the wallet. + */ + walletTipId: string; + + /** + * Secret seed used to derive planchets for this tip. + */ + secretSeed: string; + + /** + * The merchant's identifier for this tip. + */ + merchantTipId: string; + + createdTimestamp: Timestamp; + + /** + * Timestamp for when the wallet finished picking up the tip + * from the merchant. + */ + pickedUpTimestamp: Timestamp | undefined; + + /** + * Retry info, even present when the operation isn't active to allow indexing + * on the next retry timestamp. + */ + retryInfo: RetryInfo; +} + +export interface RefreshGroupRecord { + /** + * Retry info, even present when the operation isn't active to allow indexing + * on the next retry timestamp. + */ + retryInfo: RetryInfo; + + lastError: TalerErrorDetails | undefined; + + lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetails }; + + refreshGroupId: string; + + reason: RefreshReason; + + oldCoinPubs: string[]; + + refreshSessionPerCoin: (RefreshSessionRecord | undefined)[]; + + inputPerCoin: AmountJson[]; + + estimatedOutputPerCoin: AmountJson[]; + + /** + * Flag for each coin whether refreshing finished. + * If a coin can't be refreshed (remaining value too small), + * it will be marked as finished, but no refresh session will + * be created. + */ + finishedPerCoin: boolean[]; + + timestampCreated: Timestamp; + + /** + * Timestamp when the refresh session finished. + */ + timestampFinished: Timestamp | undefined; +} + +/** + * Ongoing refresh + */ +export interface RefreshSessionRecord { + /** + * 512-bit secret that can be used to derive + * the other cryptographic material for the refresh session. + * + * FIXME: We currently store the derived material, but + * should always derive it. + */ + sessionSecretSeed: string; + + /** + * Sum of the value of denominations we want + * to withdraw in this session, without fees. + */ + amountRefreshOutput: AmountJson; + + /** + * Hashed denominations of the newly requested coins. + */ + newDenoms: { + denomPubHash: string; + count: number; + }[]; + + /** + * The no-reveal-index after we've done the melting. + */ + norevealIndex?: number; +} + +/** + * Wire fee for one wire method as stored in the + * wallet's database. + */ +export interface WireFee { + /** + * Fee for wire transfers. + */ + wireFee: AmountJson; + + /** + * Fees to close and refund a reserve. + */ + closingFee: AmountJson; + + /** + * Start date of the fee. + */ + startStamp: Timestamp; + + /** + * End date of the fee. + */ + endStamp: Timestamp; + + /** + * Signature made by the exchange master key. + */ + sig: string; +} + +/** + * Record to store information about a refund event. + * + * All information about a refund is stored with the purchase, + * this event is just for the history. + * + * The event is only present for completed refunds. + */ +export interface RefundEventRecord { + timestamp: Timestamp; + merchantExecutionTimestamp: Timestamp; + refundGroupId: string; + proposalId: string; +} + +export enum RefundState { + Failed = "failed", + Applied = "applied", + Pending = "pending", +} + +/** + * State of one refund from the merchant, maintained by the wallet. + */ +export type WalletRefundItem = + | WalletRefundFailedItem + | WalletRefundPendingItem + | WalletRefundAppliedItem; + +export interface WalletRefundItemCommon { + // Execution time as claimed by the merchant + executionTime: Timestamp; + + /** + * Time when the wallet became aware of the refund. + */ + obtainedTime: Timestamp; + + refundAmount: AmountJson; + + refundFee: AmountJson; + + /** + * Upper bound on the refresh cost incurred by + * applying this refund. + * + * Might be lower in practice when two refunds on the same + * coin are refreshed in the same refresh operation. + */ + totalRefreshCostBound: AmountJson; + + coinPub: string; + + rtransactionId: number; +} + +/** + * Failed refund, either because the merchant did + * something wrong or it expired. + */ +export interface WalletRefundFailedItem extends WalletRefundItemCommon { + type: RefundState.Failed; +} + +export interface WalletRefundPendingItem extends WalletRefundItemCommon { + type: RefundState.Pending; +} + +export interface WalletRefundAppliedItem extends WalletRefundItemCommon { + type: RefundState.Applied; +} + +export enum RefundReason { + /** + * Normal refund given by the merchant. + */ + NormalRefund = "normal-refund", + /** + * Refund from an aborted payment. + */ + AbortRefund = "abort-pay-refund", +} + +/** + * Record stored for every time we successfully submitted + * a payment to the merchant (both first time and re-play). + */ +export interface PayEventRecord { + proposalId: string; + sessionId: string | undefined; + isReplay: boolean; + timestamp: Timestamp; +} + +export interface ExchangeUpdatedEventRecord { + exchangeBaseUrl: string; + timestamp: Timestamp; +} + +export interface ReserveUpdatedEventRecord { + amountReserveBalance: string; + amountExpected: string; + reservePub: string; + timestamp: Timestamp; + reserveUpdateId: string; + newHistoryTransactions: ReserveTransaction[]; +} + +export interface AllowedAuditorInfo { + auditorBaseUrl: string; + auditorPub: string; +} + +export interface AllowedExchangeInfo { + exchangeBaseUrl: string; + exchangePub: string; +} + +/** + * Data extracted from the contract terms that is relevant for payment + * processing in the wallet. + */ +export interface WalletContractData { + products?: Product[]; + summaryI18n: { [lang_tag: string]: string } | undefined; + + /** + * Fulfillment URL, or the empty string if the order has no fulfillment URL. + * + * Stored as a non-nullable string as we use this field for IndexedDB indexing. + */ + fulfillmentUrl: string; + + contractTermsHash: string; + fulfillmentMessage?: string; + fulfillmentMessageI18n?: InternationalizedString; + merchantSig: string; + merchantPub: string; + merchant: MerchantInfo; + amount: AmountJson; + orderId: string; + merchantBaseUrl: string; + summary: string; + autoRefund: Duration | undefined; + maxWireFee: AmountJson; + wireFeeAmortization: number; + payDeadline: Timestamp; + refundDeadline: Timestamp; + allowedAuditors: AllowedAuditorInfo[]; + allowedExchanges: AllowedExchangeInfo[]; + timestamp: Timestamp; + wireMethod: string; + wireInfoHash: string; + maxDepositFee: AmountJson; +} + + +export enum AbortStatus { + None = "none", + AbortRefund = "abort-refund", + AbortFinished = "abort-finished", +} + +/** + * Record that stores status information about one purchase, starting from when + * the customer accepts a proposal. Includes refund status if applicable. + */ +export interface PurchaseRecord { + /** + * Proposal ID for this purchase. Uniquely identifies the + * purchase and the proposal. + */ + proposalId: string; + + /** + * Private key for the nonce. + */ + noncePriv: string; + + /** + * Public key for the nonce. + */ + noncePub: string; + + /** + * Downloaded and parsed proposal data. + */ + download: ProposalDownload; + + /** + * Deposit permissions, available once the user has accepted the payment. + * + * This value is cached and derived from payCoinSelection. + */ + coinDepositPermissions: CoinDepositPermission[] | undefined; + + payCoinSelection: PayCoinSelection; + + /** + * Pending removals from pay coin selection. + * + * Used when a the pay coin selection needs to be changed + * because a coin became known as double-spent or invalid, + * but a new coin selection can't immediately be done, as + * there is not enough balance (e.g. when waiting for a refresh). + */ + pendingRemovedCoinPubs?: string[]; + + totalPayCost: AmountJson; + + /** + * Timestamp of the first time that sending a payment to the merchant + * for this purchase was successful. + */ + timestampFirstSuccessfulPay: Timestamp | undefined; + + merchantPaySig: string | undefined; + + /** + * When was the purchase made? + * Refers to the time that the user accepted. + */ + timestampAccept: Timestamp; + + /** + * Pending refunds for the purchase. A refund is pending + * when the merchant reports a transient error from the exchange. + */ + refunds: { [refundKey: string]: WalletRefundItem }; + + /** + * When was the last refund made? + * Set to 0 if no refund was made on the purchase. + */ + timestampLastRefundStatus: Timestamp | undefined; + + /** + * Last session signature that we submitted to /pay (if any). + */ + lastSessionId: string | undefined; + + /** + * Set for the first payment, or on re-plays. + */ + paymentSubmitPending: boolean; + + /** + * Do we need to query the merchant for the refund status + * of the payment? + */ + refundQueryRequested: boolean; + + abortStatus: AbortStatus; + + payRetryInfo: RetryInfo; + + lastPayError: TalerErrorDetails | undefined; + + /** + * Retry information for querying the refund status with the merchant. + */ + refundStatusRetryInfo: RetryInfo; + + /** + * Last error (or undefined) for querying the refund status with the merchant. + */ + lastRefundStatusError: TalerErrorDetails | undefined; + + /** + * Continue querying the refund status until this deadline has expired. + */ + autoRefundDeadline: Timestamp | undefined; +} + +/** + * Configuration key/value entries to configure + * the wallet. + */ +export interface ConfigRecord<T> { + key: string; + value: T; +} + +/** + * FIXME: Eliminate this in favor of DenomSelectionState. + */ +export interface DenominationSelectionInfo { + totalCoinValue: AmountJson; + totalWithdrawCost: AmountJson; + selectedDenoms: { + /** + * How many times do we withdraw this denomination? + */ + count: number; + denom: DenominationRecord; + }[]; +} + +/** + * Selected denominations withn some extra info. + */ +export interface DenomSelectionState { + totalCoinValue: AmountJson; + totalWithdrawCost: AmountJson; + selectedDenoms: { + denomPubHash: string; + count: number; + }[]; +} + +/** + * Group of withdrawal operations that need to be executed. + * (Either for a normal withdrawal or from a tip.) + * + * The withdrawal group record is only created after we know + * the coin selection we want to withdraw. + */ +export interface WithdrawalGroupRecord { + withdrawalGroupId: string; + + /** + * Secret seed used to derive planchets. + */ + secretSeed: string; + + reservePub: string; + + exchangeBaseUrl: string; + + /** + * When was the withdrawal operation started started? + * Timestamp in milliseconds. + */ + timestampStart: Timestamp; + + /** + * When was the withdrawal operation completed? + */ + timestampFinish?: Timestamp; + + /** + * Amount including fees (i.e. the amount subtracted from the + * reserve to withdraw all coins in this withdrawal session). + */ + rawWithdrawalAmount: AmountJson; + + denomsSel: DenomSelectionState; + + /** + * Retry info, always present even on completed operations so that indexing works. + */ + retryInfo: RetryInfo; + + lastError: TalerErrorDetails | undefined; +} + +export interface BankWithdrawUriRecord { + /** + * The withdraw URI we got from the bank. + */ + talerWithdrawUri: string; + + /** + * Reserve that was created for the withdraw URI. + */ + reservePub: string; +} + +/** + * Status of recoup operations that were grouped together. + * + * The remaining amount of involved coins should be set to zero + * in the same transaction that inserts the RecoupGroupRecord. + */ +export interface RecoupGroupRecord { + /** + * Unique identifier for the recoup group record. + */ + recoupGroupId: string; + + timestampStarted: Timestamp; + + timestampFinished: Timestamp | undefined; + + /** + * Public keys that identify the coins being recouped + * as part of this session. + * + * (Structured like this to enable multiEntry indexing in IndexedDB.) + */ + coinPubs: string[]; + + /** + * Array of flags to indicate whether the recoup finished on each individual coin. + */ + recoupFinishedPerCoin: boolean[]; + + /** + * We store old amount (i.e. before recoup) of recouped coins here, + * as the balance of a recouped coin is set to zero when the + * recoup group is created. + */ + oldAmountPerCoin: AmountJson[]; + + /** + * Public keys of coins that should be scheduled for refreshing + * after all individual recoups are done. + */ + scheduleRefreshCoins: string[]; + + /** + * Retry info. + */ + retryInfo: RetryInfo; + + /** + * Last error that occured, if any. + */ + lastError: TalerErrorDetails | undefined; +} + +export enum ImportPayloadType { + CoreSchema = "core-schema", +} + +export enum BackupProviderStatus { + PaymentRequired = "payment-required", + Ready = "ready", +} + +export interface BackupProviderRecord { + baseUrl: string; + + /** + * Terms of service of the provider. + * Might be unavailable in the DB in certain situations + * (such as loading a recovery document). + */ + terms?: { + supportedProtocolVersion: string; + annualFee: AmountString; + storageLimitInMegabytes: number; + }; + + active: boolean; + + /** + * Hash of the last encrypted backup that we already merged + * or successfully uploaded ourselves. + */ + lastBackupHash?: string; + + /** + * Clock of the last backup that we already + * merged. + */ + lastBackupClock?: number; + + lastBackupTimestamp?: Timestamp; + + /** + * Proposal that we're currently trying to pay for. + * + * (Also included in paymentProposalIds.) + */ + currentPaymentProposalId?: string; + + /** + * Proposals that were used to pay (or attempt to pay) the provider. + * + * Stored to display a history of payments to the provider, and + * to make sure that the wallet isn't overpaying. + */ + paymentProposalIds: string[]; + + /** + * Next scheduled backup. + */ + nextBackupTimestamp?: Timestamp; + + /** + * Retry info. + */ + retryInfo: RetryInfo; + + /** + * Last error that occured, if any. + */ + lastError: TalerErrorDetails | undefined; +} + +/** + * Group of deposits made by the wallet. + */ +export interface DepositGroupRecord { + depositGroupId: string; + + merchantPub: string; + merchantPriv: string; + + noncePriv: string; + noncePub: string; + + /** + * Wire information used by all deposits in this + * deposit group. + */ + wire: { + payto_uri: string; + salt: string; + }; + + /** + * Verbatim contract terms. + */ + contractTermsRaw: ContractTerms; + + contractTermsHash: string; + + payCoinSelection: PayCoinSelection; + + totalPayCost: AmountJson; + + effectiveDepositAmount: AmountJson; + + depositedPerCoin: boolean[]; + + timestampCreated: Timestamp; + + timestampFinished: Timestamp | undefined; + + lastError: TalerErrorDetails | undefined; + + /** + * Retry info. + */ + retryInfo: RetryInfo; +} + +/** + * Record for a deposits that the wallet observed + * as a result of double spending, but which is not + * present in the wallet's own database otherwise. + */ +export interface GhostDepositGroupRecord { + /** + * When multiple deposits for the same contract terms hash + * have a different timestamp, we choose the earliest one. + */ + timestamp: Timestamp; + + contractTermsHash: string; + + deposits: { + coinPub: string; + amount: AmountString; + timestamp: Timestamp; + depositFee: AmountString; + merchantPub: string; + coinSig: string; + wireHash: string; + }[]; +} + +class ExchangesStore extends Store<"exchanges", ExchangeRecord> { + constructor() { + super("exchanges", { keyPath: "baseUrl" }); + } +} + +class CoinsStore extends Store<"coins", CoinRecord> { + constructor() { + super("coins", { keyPath: "coinPub" }); + } + + exchangeBaseUrlIndex = new Index< + "coins", + "exchangeBaseUrl", + string, + CoinRecord + >(this, "exchangeBaseUrl", "exchangeBaseUrl"); + + denomPubHashIndex = new Index< + "coins", + "denomPubHashIndex", + string, + CoinRecord + >(this, "denomPubHashIndex", "denomPubHash"); + + coinEvHashIndex = new Index<"coins", "coinEvHashIndex", string, CoinRecord>( + this, + "coinEvHashIndex", + "coinEvHash", + ); +} + +class ProposalsStore extends Store<"proposals", ProposalRecord> { + constructor() { + super("proposals", { keyPath: "proposalId" }); + } + urlAndOrderIdIndex = new Index< + "proposals", + "urlIndex", + string, + ProposalRecord + >(this, "urlIndex", ["merchantBaseUrl", "orderId"]); +} + +class PurchasesStore extends Store<"purchases", PurchaseRecord> { + constructor() { + super("purchases", { keyPath: "proposalId" }); + } + + fulfillmentUrlIndex = new Index< + "purchases", + "fulfillmentUrlIndex", + string, + PurchaseRecord + >(this, "fulfillmentUrlIndex", "download.contractData.fulfillmentUrl"); + + orderIdIndex = new Index<"purchases", "orderIdIndex", string, PurchaseRecord>( + this, + "orderIdIndex", + ["download.contractData.merchantBaseUrl", "download.contractData.orderId"], + ); +} + +class DenominationsStore extends Store<"denominations", DenominationRecord> { + constructor() { + // cast needed because of bug in type annotations + super("denominations", { + keyPath: (["exchangeBaseUrl", "denomPubHash"] as any) as IDBKeyPath, + }); + } + exchangeBaseUrlIndex = new Index< + "denominations", + "exchangeBaseUrlIndex", + string, + DenominationRecord + >(this, "exchangeBaseUrlIndex", "exchangeBaseUrl"); +} + +class CurrenciesStore extends Store<"currencies", CurrencyRecord> { + constructor() { + super("currencies", { keyPath: "name" }); + } +} + +class ConfigStore extends Store<"config", ConfigRecord<any>> { + constructor() { + super("config", { keyPath: "key" }); + } +} + +class ReservesStore extends Store<"reserves", ReserveRecord> { + constructor() { + super("reserves", { keyPath: "reservePub" }); + } +} + +class TipsStore extends Store<"tips", TipRecord> { + constructor() { + super("tips", { keyPath: "walletTipId" }); + } + // Added in version 2 + byMerchantTipIdAndBaseUrl = new Index< + "tips", + "tipsByMerchantTipIdAndOriginIndex", + [string, string], + TipRecord + >(this, "tipsByMerchantTipIdAndOriginIndex", [ + "merchantTipId", + "merchantBaseUrl", + ]); +} + +class WithdrawalGroupsStore extends Store< + "withdrawals", + WithdrawalGroupRecord +> { + constructor() { + super("withdrawals", { keyPath: "withdrawalGroupId" }); + } + byReservePub = new Index< + "withdrawals", + "withdrawalsByReserveIndex", + string, + WithdrawalGroupRecord + >(this, "withdrawalsByReserveIndex", "reservePub"); +} + +class PlanchetsStore extends Store<"planchets", PlanchetRecord> { + constructor() { + super("planchets", { keyPath: "coinPub" }); + } + byGroupAndIndex = new Index< + "planchets", + "withdrawalGroupAndCoinIdxIndex", + string, + PlanchetRecord + >(this, "withdrawalGroupAndCoinIdxIndex", ["withdrawalGroupId", "coinIdx"]); + byGroup = new Index< + "planchets", + "withdrawalGroupIndex", + string, + PlanchetRecord + >(this, "withdrawalGroupIndex", "withdrawalGroupId"); + + coinEvHashIndex = new Index< + "planchets", + "coinEvHashIndex", + string, + PlanchetRecord + >(this, "coinEvHashIndex", "coinEvHash"); +} + +/** + * This store is effectively a materialized index for + * reserve records that are for a bank-integrated withdrawal. + */ +class BankWithdrawUrisStore extends Store< + "bankWithdrawUris", + BankWithdrawUriRecord +> { + constructor() { + super("bankWithdrawUris", { keyPath: "talerWithdrawUri" }); + } +} + +/** + */ +class BackupProvidersStore extends Store< + "backupProviders", + BackupProviderRecord +> { + constructor() { + super("backupProviders", { keyPath: "baseUrl" }); + } +} + +class DepositGroupsStore extends Store<"depositGroups", DepositGroupRecord> { + constructor() { + super("depositGroups", { keyPath: "depositGroupId" }); + } +} + +/** + * The stores and indices for the wallet database. + */ +export const Stores = { + coins: new CoinsStore(), + config: new ConfigStore(), + currencies: new CurrenciesStore(), + denominations: new DenominationsStore(), + exchanges: new ExchangesStore(), + proposals: new ProposalsStore(), + refreshGroups: new Store<"refreshGroups", RefreshGroupRecord>( + "refreshGroups", + { + keyPath: "refreshGroupId", + }, + ), + recoupGroups: new Store<"recoupGroups", RecoupGroupRecord>("recoupGroups", { + keyPath: "recoupGroupId", + }), + reserves: new ReservesStore(), + purchases: new PurchasesStore(), + tips: new TipsStore(), + withdrawalGroups: new WithdrawalGroupsStore(), + planchets: new PlanchetsStore(), + bankWithdrawUris: new BankWithdrawUrisStore(), + backupProviders: new BackupProvidersStore(), + depositGroups: new DepositGroupsStore(), + ghostDepositGroups: new Store<"ghostDepositGroups", GhostDepositGroupRecord>( + "ghostDepositGroups", + { + keyPath: "contractTermsHash", + }, + ), +}; + +export class MetaConfigStore extends Store<"metaConfig", ConfigRecord<any>> { + constructor() { + super("metaConfig", { keyPath: "key" }); + } +} + +export const MetaStores = { + metaConfig: new MetaConfigStore(), +}; diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts @@ -28,11 +28,10 @@ import { import { RequestThrottler } from "../util/RequestThrottler"; import Axios, { AxiosResponse } from "axios"; import { OperationFailedError, makeErrorDetails } from "../operations/errors"; -import { TalerErrorCode } from "../TalerErrorCode"; import { URL } from "../util/url"; import { Logger } from "../util/logging"; import { bytesToString } from "../crypto/talerCrypto"; -import { j2s } from "../util/helpers"; +import { TalerErrorCode } from "@gnu-taler/taler-util"; const logger = new Logger("NodeHttpLib.ts"); diff --git a/packages/taler-wallet-core/src/headless/helpers.ts b/packages/taler-wallet-core/src/headless/helpers.ts @@ -23,18 +23,20 @@ * Imports. */ import { Wallet } from "../wallet"; -import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "@gnu-taler/idb-bridge"; +import { + MemoryBackend, + BridgeIDBFactory, + shimIndexedDB, +} from "@gnu-taler/idb-bridge"; import { openTalerDatabase } from "../db"; import { HttpRequestLibrary } from "../util/http"; import fs from "fs"; import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorker"; -import { WalletNotification } from "../types/notifications"; -import { Database } from "../util/query"; import { NodeHttpLib } from "./NodeHttpLib"; import { Logger } from "../util/logging"; import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker"; -import type { IDBFactory } from "@gnu-taler/idb-bridge/lib/idbtypes"; -import { Stores } from "../types/dbTypes"; +import type { IDBFactory } from "@gnu-taler/idb-bridge"; +import { WalletNotification } from "@gnu-taler/taler-util"; const logger = new Logger("headless/helpers.ts"); diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts @@ -21,7 +21,6 @@ export { Wallet } from "./wallet"; // Errors -export { TalerErrorCode } from "./TalerErrorCode"; export * from "./operations/errors"; // Utils for using the wallet under node @@ -34,7 +33,6 @@ export { export * from "./operations/versions"; export * from "./db"; -export * from "./types/dbTypes"; // Internationalization export * from "./i18n"; @@ -47,22 +45,10 @@ export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi"; export * from "./crypto/talerCrypto"; // Util functionality -export { Amounts, AmountJson } from "./util/amounts"; export { Logger } from "./util/logging"; -export { Configuration } from "./util/talerconfig"; export { URL } from "./util/url"; -export * from "./util/codec"; export * from "./util/promiseUtils"; export * from "./util/query"; export * from "./util/http"; -export * from "./util/payto"; -export * from "./util/testvectors"; -export * from "./util/taleruri"; -export * from "./util/time"; -// Types -export * from "./types/talerTypes"; -export * from "./types/walletTypes"; -export * from "./types/notifications"; -export * from "./types/transactionsTypes"; -export * from "./types/pendingTypes"; +export * from "./pending-types"; +\ No newline at end of file diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -14,12 +14,14 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Stores, Amounts, CoinSourceType, CoinStatus, RefundState, AbortStatus, ProposalStatus, getTimestampNow, encodeCrock, stringToBytes, getRandomBytes } from "../.."; import { hash } from "../../crypto/primitives/nacl-fast"; -import { WalletBackupContentV1, BackupExchange, BackupCoin, BackupDenomination, BackupReserve, BackupPurchase, BackupProposal, BackupRefreshGroup, BackupBackupProvider, BackupTip, BackupRecoupGroup, BackupWithdrawalGroup, BackupBackupProviderTerms, BackupCoinSource, BackupCoinSourceType, BackupExchangeWireFee, BackupRefundItem, BackupRefundState, BackupProposalStatus, BackupRefreshOldCoin, BackupRefreshSession } from "../../types/backupTypes"; +import { WalletBackupContentV1, BackupExchange, BackupCoin, BackupDenomination, BackupReserve, BackupPurchase, BackupProposal, BackupRefreshGroup, BackupBackupProvider, BackupTip, BackupRecoupGroup, BackupWithdrawalGroup, BackupBackupProviderTerms, BackupCoinSource, BackupCoinSourceType, BackupExchangeWireFee, BackupRefundItem, BackupRefundState, BackupProposalStatus, BackupRefreshOldCoin, BackupRefreshSession } from "@gnu-taler/taler-util"; import { canonicalizeBaseUrl, canonicalJson } from "../../util/helpers"; import { InternalWalletState } from "../state"; import { provideBackupState, getWalletBackupState, WALLET_BACKUP_STATE_KEY } from "./state"; +import { Amounts, getTimestampNow } from "@gnu-taler/taler-util"; +import { Stores, CoinSourceType, CoinStatus, RefundState, AbortStatus, ProposalStatus } from "../../db.js"; +import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js"; /** * Implementation of wallet backups (export/import/upload) and sync diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -14,40 +14,9 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - AbortStatus, - AmountJson, - Amounts, - codecForContractTerms, - CoinSource, - CoinSourceType, - CoinStatus, - DenominationStatus, - DenomSelectionState, - ExchangeUpdateStatus, - ExchangeWireInfo, - getTimestampNow, - ProposalDownload, - ProposalStatus, - RefreshReason, - RefreshSessionRecord, - RefundState, - ReserveBankInfo, - ReserveRecordStatus, - Stores, - TransactionHandle, - WalletContractData, - WalletRefundItem, -} from "../.."; -import { - BackupCoinSourceType, - BackupDenomSel, - BackupProposalStatus, - BackupPurchase, - BackupRefreshReason, - BackupRefundState, - WalletBackupContentV1, -} from "../../types/backupTypes"; +import { BackupPurchase, AmountJson, Amounts, BackupDenomSel, WalletBackupContentV1, getTimestampNow, BackupCoinSourceType, BackupProposalStatus, codecForContractTerms, BackupRefundState, RefreshReason, BackupRefreshReason } from "@gnu-taler/taler-util"; +import { Stores, WalletContractData, DenomSelectionState, ExchangeWireInfo, ExchangeUpdateStatus, DenominationStatus, CoinSource, CoinSourceType, CoinStatus, ReserveBankInfo, ReserveRecordStatus, ProposalDownload, ProposalStatus, WalletRefundItem, RefundState, AbortStatus, RefreshSessionRecord } from "../../db.js"; +import { TransactionHandle } from "../../index.js"; import { PayCoinSelection } from "../../util/coinSelection"; import { j2s } from "../../util/helpers"; import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants"; diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -25,15 +25,14 @@ * Imports. */ import { InternalWalletState } from "../state"; -import { WalletBackupContentV1 } from "../../types/backupTypes"; +import { AmountString, BackupRecovery, codecForAmountString, WalletBackupContentV1 } from "@gnu-taler/taler-util"; import { TransactionHandle } from "../../util/query"; import { BackupProviderRecord, ConfigRecord, Stores, -} from "../../types/dbTypes"; +} from "../../db.js"; import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants"; -import { codecForAmountString } from "../../util/amounts"; import { bytesToString, decodeCrock, @@ -51,9 +50,8 @@ import { getTimestampNow, Timestamp, timestampAddDuration, -} from "../../util/time"; +} from "@gnu-taler/taler-util"; import { URL } from "../../util/url"; -import { AmountString } from "../../types/talerTypes"; import { buildCodecForObject, Codec, @@ -61,7 +59,7 @@ import { codecForNumber, codecForString, codecOptional, -} from "../../util/codec"; +} from "@gnu-taler/taler-util"; import { HttpResponseStatus, readSuccessResponseJsonOrThrow, @@ -77,7 +75,7 @@ import { RecoveryLoadRequest, RecoveryMergeStrategy, TalerErrorDetails, -} from "../../types/walletTypes"; +} from "@gnu-taler/taler-util"; import { CryptoApi } from "../../crypto/workers/cryptoApi"; import { secretbox, secretbox_open } from "../../crypto/primitives/nacl-fast"; import { checkPaymentByProposalId, confirmPay, preparePayForUri } from "../pay"; @@ -89,7 +87,6 @@ import { getWalletBackupState, WalletBackupConfState, } from "./state"; -import { PaymentStatus } from "../../types/transactionsTypes"; const logger = new Logger("operations/backup.ts"); @@ -639,13 +636,6 @@ export async function getBackupInfo( }; } -export interface BackupRecovery { - walletRootPriv: string; - providers: { - url: string; - }[]; -} - /** * Get information about the current state of wallet backups. */ diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts @@ -14,14 +14,9 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - ConfigRecord, - encodeCrock, - getRandomBytes, - Stores, - Timestamp, - TransactionHandle, -} from "../.."; +import { Timestamp } from "@gnu-taler/taler-util"; +import { ConfigRecord, Stores } from "../../db.js"; +import { getRandomBytes, encodeCrock, TransactionHandle } from "../../index.js"; import { checkDbInvariant } from "../../util/invariants"; import { InternalWalletState } from "../state"; diff --git a/packages/taler-wallet-core/src/operations/balance.ts b/packages/taler-wallet-core/src/operations/balance.ts @@ -17,13 +17,11 @@ /** * Imports. */ -import { BalancesResponse } from "../types/walletTypes"; -import { TransactionHandle } from "../util/query"; -import { InternalWalletState } from "./state"; -import { Stores, CoinStatus } from "../types/dbTypes"; -import * as Amounts from "../util/amounts"; -import { AmountJson } from "../util/amounts"; +import { AmountJson, BalancesResponse, Amounts } from "@gnu-taler/taler-util"; +import { Stores, CoinStatus } from "../db.js"; +import { TransactionHandle } from "../index.js"; import { Logger } from "../util/logging"; +import { InternalWalletState } from "./state.js"; const logger = new Logger("withdraw.ts"); diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts @@ -14,42 +14,37 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - Amounts, - CreateDepositGroupRequest, - guardOperationException, - Logger, - NotificationType, - TalerErrorDetails, -} from ".."; import { kdf } from "../crypto/primitives/kdf"; import { encodeCrock, getRandomBytes, stringToBytes, } from "../crypto/talerCrypto"; -import { DepositGroupRecord, Stores } from "../types/dbTypes"; -import { ContractTerms } from "../types/talerTypes"; -import { CreateDepositGroupResponse, TrackDepositGroupRequest, TrackDepositGroupResponse } from "../types/walletTypes"; -import { - buildCodecForObject, - Codec, - codecForString, - codecOptional, -} from "../util/codec"; import { selectPayCoins } from "../util/coinSelection"; import { canonicalJson } from "../util/helpers"; import { readSuccessResponseJsonOrThrow } from "../util/http"; -import { parsePaytoUri } from "../util/payto"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries"; import { + Amounts, + buildCodecForObject, + Codec, + codecForString, codecForTimestamp, + codecOptional, + ContractTerms, + CreateDepositGroupRequest, + CreateDepositGroupResponse, durationFromSpec, getTimestampNow, + NotificationType, + parsePaytoUri, + TalerErrorDetails, Timestamp, timestampAddDuration, timestampTruncateToSecond, -} from "../util/time"; + TrackDepositGroupRequest, + TrackDepositGroupResponse, +} from "@gnu-taler/taler-util"; import { URL } from "../util/url"; import { applyCoinSpend, @@ -60,6 +55,9 @@ import { getTotalPaymentCost, } from "./pay"; import { InternalWalletState } from "./state"; +import { Logger } from "../util/logging.js"; +import { DepositGroupRecord, Stores } from "../db.js"; +import { guardOperationException } from "./errors.js"; /** * Logger. @@ -242,7 +240,6 @@ async function processDepositGroupImpl( }); } - export async function trackDepositGroup( ws: InternalWalletState, req: TrackDepositGroupRequest, @@ -384,7 +381,6 @@ export async function createDepositGroup( prevPayCoins: [], }); - if (!payCoinSel) { throw Error("insufficient funds"); } @@ -437,4 +433,4 @@ export async function createDepositGroup( await ws.db.put(Stores.depositGroups, depositGroup); return { depositGroupId }; -} +} +\ No newline at end of file diff --git a/packages/taler-wallet-core/src/operations/errors.ts b/packages/taler-wallet-core/src/operations/errors.ts @@ -23,8 +23,7 @@ /** * Imports. */ -import { TalerErrorDetails } from "../types/walletTypes"; -import { TalerErrorCode } from "../TalerErrorCode"; +import { TalerErrorCode, TalerErrorDetails } from "@gnu-taler/taler-util"; /** * This exception is there to let the caller know that an error happened, diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -14,54 +14,54 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { InternalWalletState } from "./state"; +/** + * Imports. + */ import { - Denomination, + Amounts, codecForExchangeKeysJson, codecForExchangeWireJson, -} from "../types/talerTypes"; -import { TalerErrorDetails } from "../types/walletTypes"; + compare, + Denomination, + Duration, + durationFromSpec, + getTimestampNow, + isTimestampExpired, + NotificationType, + parsePaytoUri, + TalerErrorCode, + TalerErrorDetails, +} from "@gnu-taler/taler-util"; import { - ExchangeRecord, - ExchangeUpdateStatus, - Stores, DenominationRecord, DenominationStatus, + Stores, + ExchangeRecord, + ExchangeUpdateStatus, WireFee, ExchangeUpdateReason, - MetaStores, -} from "../types/dbTypes"; -import { canonicalizeBaseUrl, j2s } from "../util/helpers"; -import * as Amounts from "../util/amounts"; -import { parsePaytoUri } from "../util/payto"; +} from "../db.js"; import { + Logger, + URL, + readSuccessResponseJsonOrThrow, + getExpiryTimestamp, + readSuccessResponseTextOrThrow, +} from "../index.js"; +import { j2s, canonicalizeBaseUrl } from "../util/helpers.js"; +import { checkDbInvariant } from "../util/invariants.js"; +import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries.js"; +import { + makeErrorDetails, OperationFailedAndReportedError, guardOperationException, - makeErrorDetails, -} from "./errors"; +} from "./errors.js"; +import { createRecoupGroup, processRecoupGroup } from "./recoup.js"; +import { InternalWalletState } from "./state.js"; import { WALLET_CACHE_BREAKER_CLIENT_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, -} from "./versions"; -import { - getTimestampNow, - Duration, - isTimestampExpired, - durationFromSpec, -} from "../util/time"; -import { compare } from "../util/libtoolVersion"; -import { createRecoupGroup, processRecoupGroup } from "./recoup"; -import { TalerErrorCode } from "../TalerErrorCode"; -import { - readSuccessResponseJsonOrThrow, - readSuccessResponseTextOrThrow, - getExpiryTimestamp, -} from "../util/http"; -import { Logger } from "../util/logging"; -import { URL } from "../util/url"; -import { checkDbInvariant } from "../util/invariants"; -import { NotificationType } from "../types/notifications"; -import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries"; +} from "./versions.js"; const logger = new Logger("exchanges.ts"); diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts @@ -24,77 +24,14 @@ /** * Imports. */ +import { AmountJson, Amounts, timestampIsBetween, getTimestampNow, isTimestampExpired, Timestamp, RefreshReason, CoinDepositPermission, NotificationType, TalerErrorDetails, Duration, durationMax, durationMin, durationMul, ContractTerms, codecForProposal, TalerErrorCode, codecForContractTerms, timestampAddDuration, ConfirmPayResult, ConfirmPayResultType, codecForMerchantPayResponse, PreparePayResult, PreparePayResultType, parsePayUri } from "@gnu-taler/taler-util"; import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto"; -import { - CoinStatus, - ProposalRecord, - ProposalStatus, - PurchaseRecord, - Stores, - WalletContractData, - CoinRecord, - DenominationRecord, - AbortStatus, - AllowedExchangeInfo, - AllowedAuditorInfo, -} from "../types/dbTypes"; -import { NotificationType } from "../types/notifications"; -import { - codecForProposal, - codecForContractTerms, - CoinDepositPermission, - codecForMerchantPayResponse, - ContractTerms, -} from "../types/talerTypes"; -import { - ConfirmPayResult, - TalerErrorDetails, - PreparePayResult, - RefreshReason, - PreparePayResultType, - ConfirmPayResultType, -} from "../types/walletTypes"; -import { Amounts } from "../util/amounts"; -import { AmountJson } from "../util/amounts"; -import { Logger } from "../util/logging"; -import { parsePayUri } from "../util/taleruri"; -import { - guardOperationException, - makeErrorDetails, - OperationFailedAndReportedError, - OperationFailedError, -} from "./errors"; -import { createRefreshGroup, getTotalRefreshCost } from "./refresh"; -import { InternalWalletState, EXCHANGE_COINS_LOCK } from "./state"; -import { - getTimestampNow, - timestampAddDuration, - Duration, - durationMax, - durationMin, - isTimestampExpired, - durationMul, - Timestamp, - timestampIsBetween, -} from "../util/time"; -import { strcmp, canonicalJson } from "../util/helpers"; -import { - readSuccessResponseJsonOrThrow, - throwUnexpectedRequestError, - getHttpResponseErrorDetails, - readSuccessResponseJsonOrErrorCode, - HttpResponseStatus, - readTalerErrorResponse, -} from "../util/http"; -import { TalerErrorCode } from "../TalerErrorCode"; -import { URL } from "../util/url"; -import { - initRetryInfo, - updateRetryInfoTimeout, - getRetryDuration, -} from "../util/retries"; -import { TransactionHandle } from "../util/query"; -import { PayCoinSelection, CoinCandidateSelection, AvailableCoinInfo, selectPayCoins } from "../util/coinSelection"; +import { AbortStatus, AllowedAuditorInfo, AllowedExchangeInfo, CoinRecord, CoinStatus, DenominationRecord, getHttpResponseErrorDetails, guardOperationException, HttpResponseStatus, Logger, makeErrorDetails, OperationFailedAndReportedError, OperationFailedError, ProposalRecord, ProposalStatus, PurchaseRecord, readSuccessResponseJsonOrErrorCode, readSuccessResponseJsonOrThrow, readTalerErrorResponse, Stores, throwUnexpectedRequestError, TransactionHandle, URL, WalletContractData } from "../index.js"; +import { PayCoinSelection, CoinCandidateSelection, AvailableCoinInfo, selectPayCoins } from "../util/coinSelection.js"; +import { canonicalJson } from "../util/helpers.js"; +import { initRetryInfo, updateRetryInfoTimeout, getRetryDuration } from "../util/retries.js"; +import { getTotalRefreshCost, createRefreshGroup } from "./refresh.js"; +import { InternalWalletState, EXCHANGE_COINS_LOCK } from "./state.js"; /** * Logger. diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts @@ -23,20 +23,20 @@ import { ReserveRecordStatus, Stores, AbortStatus, -} from "../types/dbTypes"; +} from "../db.js"; import { PendingOperationsResponse, PendingOperationType, ExchangeUpdateOperationStage, ReserveType, -} from "../types/pendingTypes"; +} from "../pending-types"; import { Duration, getTimestampNow, Timestamp, getDurationRemaining, durationMin, -} from "../util/time"; +} from "@gnu-taler/taler-util"; import { Store, TransactionHandle } from "../util/query"; import { InternalWalletState } from "./state"; import { getBalancesInsideTransaction } from "./balance"; diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts @@ -24,26 +24,14 @@ /** * Imports. */ +import { Amounts, codecForRecoupConfirmation, getTimestampNow, NotificationType, RefreshReason, TalerErrorDetails } from "@gnu-taler/taler-util"; import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto"; -import { - CoinRecord, - CoinSourceType, - CoinStatus, - RecoupGroupRecord, - RefreshCoinSource, - ReserveRecordStatus, - Stores, - WithdrawCoinSource, -} from "../types/dbTypes"; -import { NotificationType } from "../types/notifications"; -import { codecForRecoupConfirmation } from "../types/talerTypes"; -import { RefreshReason, TalerErrorDetails } from "../types/walletTypes"; -import { Amounts } from "../util/amounts"; +import { CoinRecord, CoinSourceType, CoinStatus, RecoupGroupRecord, RefreshCoinSource, ReserveRecordStatus, Stores, WithdrawCoinSource } from "../db.js"; + import { readSuccessResponseJsonOrThrow } from "../util/http"; import { Logger } from "../util/logging"; import { TransactionHandle } from "../util/query"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries"; -import { getTimestampNow } from "../util/time"; import { URL } from "../util/url"; import { guardOperationException } from "./errors"; import { createRefreshGroup, processRefreshGroup } from "./refresh"; diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts @@ -15,7 +15,6 @@ */ import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto"; -import { RefreshNewDenomInfo } from "../types/cryptoTypes"; import { CoinRecord, CoinSourceType, @@ -24,19 +23,17 @@ import { RefreshGroupRecord, RefreshPlanchet, Stores, -} from "../types/dbTypes"; -import { NotificationType } from "../types/notifications"; +} from "../db.js"; import { codecForExchangeMeltResponse, codecForExchangeRevealResponse, -} from "../types/talerTypes"; -import { CoinPublicKey, + NotificationType, RefreshGroupId, RefreshReason, TalerErrorDetails, -} from "../types/walletTypes"; -import { AmountJson, Amounts } from "../util/amounts"; +} from "@gnu-taler/taler-util"; +import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { amountToPretty } from "../util/helpers"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { checkDbInvariant } from "../util/invariants"; @@ -53,12 +50,13 @@ import { timestampAddDuration, timestampDifference, timestampMin, -} from "../util/time"; +} from "@gnu-taler/taler-util"; import { URL } from "../util/url"; import { guardOperationException } from "./errors"; import { updateExchangeFromUrl } from "./exchanges"; import { EXCHANGE_COINS_LOCK, InternalWalletState } from "./state"; import { isWithdrawableDenom, selectWithdrawalDenominations } from "./withdraw"; +import { RefreshNewDenomInfo } from "../crypto/cryptoTypes.js"; const logger = new Logger("refresh.ts"); diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts @@ -24,48 +24,37 @@ * Imports. */ import { InternalWalletState } from "./state"; -import { - TalerErrorDetails, - RefreshReason, - CoinPublicKey, - ApplyRefundResponse, -} from "../types/walletTypes"; -import { - Stores, - CoinStatus, - RefundReason, - RefundState, - PurchaseRecord, - AbortStatus, -} from "../types/dbTypes"; -import { NotificationType } from "../types/notifications"; -import { parseRefundUri } from "../util/taleruri"; -import { createRefreshGroup, getTotalRefreshCost } from "./refresh"; -import { Amounts, AmountJson } from "../util/amounts"; -import { - MerchantCoinRefundStatus, - MerchantCoinRefundSuccessStatus, - MerchantCoinRefundFailureStatus, - codecForMerchantOrderRefundPickupResponse, - AbortRequest, - AbortingCoin, - codecForMerchantAbortPayRefundStatus, - codecForAbortResponse, -} from "../types/talerTypes"; import { guardOperationException } from "./errors"; import { getTimestampNow, Timestamp, durationAdd, timestampAddDuration, -} from "../util/time"; + TalerErrorDetails, + AbortingCoin, + AbortRequest, + AmountJson, + Amounts, + ApplyRefundResponse, + codecForAbortResponse, + codecForMerchantOrderRefundPickupResponse, + CoinPublicKey, + MerchantCoinRefundFailureStatus, + MerchantCoinRefundStatus, + MerchantCoinRefundSuccessStatus, + NotificationType, + parseRefundUri, + RefreshReason, +} from "@gnu-taler/taler-util"; import { Logger } from "../util/logging"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { TransactionHandle } from "../util/query"; import { URL } from "../util/url"; import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries"; import { checkDbInvariant } from "../util/invariants"; -import { TalerErrorCode } from "../TalerErrorCode"; +import { TalerErrorCode } from "@gnu-taler/taler-util"; +import { Stores, PurchaseRecord, CoinStatus, RefundState, AbortStatus, RefundReason } from "../db.js"; +import { getTotalRefreshCost, createRefreshGroup } from "./refresh.js"; const logger = new Logger("refund.ts"); diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts @@ -19,69 +19,29 @@ import { CreateReserveResponse, TalerErrorDetails, AcceptWithdrawalResponse, -} from "../types/walletTypes"; -import { canonicalizeBaseUrl } from "../util/helpers"; -import { InternalWalletState } from "./state"; -import { - ReserveRecordStatus, - ReserveRecord, - CurrencyRecord, - Stores, - WithdrawalGroupRecord, - ReserveBankInfo, -} from "../types/dbTypes"; -import { Logger } from "../util/logging"; -import { Amounts } from "../util/amounts"; -import { - updateExchangeFromUrl, - getExchangeTrust, - getExchangePaytoUri, -} from "./exchanges"; -import { - codecForWithdrawOperationStatusResponse, + Amounts, codecForBankWithdrawalOperationPostResponse, -} from "../types/talerTypes"; -import { assertUnreachable } from "../util/assertUnreachable"; -import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto"; -import { randomBytes } from "../crypto/primitives/nacl-fast"; -import { - processWithdrawGroup, - getBankWithdrawalInfo, - denomSelectionInfoToState, - updateWithdrawalDenoms, - selectWithdrawalDenominations, - getCandidateWithdrawalDenoms, -} from "./withdraw"; -import { - guardOperationException, - OperationFailedAndReportedError, - makeErrorDetails, - OperationFailedError, -} from "./errors"; -import { NotificationType } from "../types/notifications"; -import { codecForReserveStatus } from "../types/ReserveStatus"; -import { - getTimestampNow, + codecForReserveStatus, + codecForWithdrawOperationStatusResponse, Duration, - durationMin, durationMax, -} from "../util/time"; -import { TransactionHandle } from "../util/query"; -import { addPaytoQueryParams } from "../util/payto"; -import { TalerErrorCode } from "../TalerErrorCode"; -import { - readSuccessResponseJsonOrErrorCode, - throwUnexpectedRequestError, - readSuccessResponseJsonOrThrow, -} from "../util/http"; -import { codecForAny } from "../util/codec"; -import { URL } from "../util/url"; -import { - initRetryInfo, - getRetryDuration, - updateRetryInfoTimeout, -} from "../util/retries"; -import { ReserveTransactionType } from "../types/ReserveTransaction"; + durationMin, + getTimestampNow, + NotificationType, + ReserveTransactionType, + TalerErrorCode, + addPaytoQueryParams, +} from "@gnu-taler/taler-util"; +import { randomBytes } from "../crypto/primitives/nacl-fast.js"; +import { Stores, ReserveRecordStatus, ReserveBankInfo, ReserveRecord, CurrencyRecord, WithdrawalGroupRecord } from "../db.js"; +import { Logger, encodeCrock, getRandomBytes, readSuccessResponseJsonOrThrow, URL, readSuccessResponseJsonOrErrorCode, throwUnexpectedRequestError, TransactionHandle } from "../index.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; +import { canonicalizeBaseUrl } from "../util/helpers.js"; +import { initRetryInfo, getRetryDuration, updateRetryInfoTimeout } from "../util/retries.js"; +import { guardOperationException, OperationFailedError } from "./errors.js"; +import { updateExchangeFromUrl, getExchangeTrust, getExchangePaytoUri } from "./exchanges.js"; +import { InternalWalletState } from "./state.js"; +import { updateWithdrawalDenoms, getCandidateWithdrawalDenoms, selectWithdrawalDenominations, denomSelectionInfoToState, processWithdrawGroup, getBankWithdrawalInfo } from "./withdraw.js"; const logger = new Logger("reserves.ts"); diff --git a/packages/taler-wallet-core/src/operations/state.ts b/packages/taler-wallet-core/src/operations/state.ts @@ -14,16 +14,15 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +/** + * Imports. + */ +import { WalletNotification, BalancesResponse } from "@gnu-taler/taler-util"; +import { Stores } from "../db.js"; +import { Logger, CryptoApi, OpenedPromise, Database, CryptoWorkerFactory, openPromise } from "../index.js"; +import { PendingOperationsResponse } from "../pending-types.js"; +import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo.js"; import { HttpRequestLibrary } from "../util/http"; -import { BalancesResponse } from "../types/walletTypes"; -import { CryptoApi, CryptoWorkerFactory } from "../crypto/workers/cryptoApi"; -import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo"; -import { Logger } from "../util/logging"; -import { PendingOperationsResponse } from "../types/pendingTypes"; -import { WalletNotification } from "../types/notifications"; -import { Database } from "../util/query"; -import { openPromise, OpenedPromise } from "../util/promiseUtils"; -import { Stores } from "../types/dbTypes"; type NotificationListener = (n: WalletNotification) => void; diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts @@ -14,28 +14,20 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +/** + * Imports. + */ import { Logger } from "../util/logging"; import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, checkSuccessResponseOrThrow, } from "../util/http"; -import { codecForAny } from "../util/codec"; -import { - AmountString, - CheckPaymentResponse, - codecForCheckPaymentResponse, -} from "../types/talerTypes"; -import { InternalWalletState } from "./state"; -import { createTalerWithdrawReserve } from "./reserves"; -import { URL } from "../util/url"; -import { Wallet } from "../wallet"; -import { Amounts } from "../util/amounts"; -import { - TestPayArgs, - PreparePayResultType, - IntegrationTestArgs, -} from "../types/walletTypes"; +import { AmountString, codecForAny, CheckPaymentResponse, codecForCheckPaymentResponse, IntegrationTestArgs, Amounts, TestPayArgs, PreparePayResultType } from "@gnu-taler/taler-util"; +import { URL } from "../index.js"; +import { Wallet } from "../wallet.js"; +import { createTalerWithdrawReserve } from "./reserves.js"; +import { InternalWalletState } from "./state.js"; const logger = new Logger("operations/testing.ts"); diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts @@ -14,45 +14,50 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { InternalWalletState } from "./state"; -import { parseTipUri } from "../util/taleruri"; -import { PrepareTipResult, TalerErrorDetails } from "../types/walletTypes"; +/** + * Imports. + */ import { - TipPlanchetDetail, + PrepareTipResult, + parseTipUri, codecForTipPickupGetResponse, + Amounts, + getTimestampNow, + TalerErrorDetails, + NotificationType, + TipPlanchetDetail, + TalerErrorCode, codecForTipResponse, -} from "../types/talerTypes"; -import * as Amounts from "../util/amounts"; +} from "@gnu-taler/taler-util"; +import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js"; import { Stores, + DenominationRecord, CoinRecord, CoinSourceType, CoinStatus, - DenominationRecord, -} from "../types/dbTypes"; +} from "../db.js"; +import { + Logger, + URL, + readSuccessResponseJsonOrThrow, + encodeCrock, + getRandomBytes, + getHttpResponseErrorDetails, +} from "../index.js"; +import { j2s } from "../util/helpers.js"; +import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; +import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; +import { guardOperationException, makeErrorDetails } from "./errors.js"; +import { updateExchangeFromUrl } from "./exchanges.js"; +import { InternalWalletState } from "./state"; import { getExchangeWithdrawalInfo, - denomSelectionInfoToState, updateWithdrawalDenoms, getCandidateWithdrawalDenoms, selectWithdrawalDenominations, -} from "./withdraw"; -import { updateExchangeFromUrl } from "./exchanges"; -import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto"; -import { guardOperationException, makeErrorDetails } from "./errors"; -import { NotificationType } from "../types/notifications"; -import { getTimestampNow } from "../util/time"; -import { - getHttpResponseErrorDetails, - readSuccessResponseJsonOrThrow, -} from "../util/http"; -import { URL } from "../util/url"; -import { Logger } from "../util/logging"; -import { checkDbInvariant, checkLogicInvariant } from "../util/invariants"; -import { TalerErrorCode } from "../TalerErrorCode"; -import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries"; -import { j2s } from "../util/helpers"; -import { DerivedTipPlanchet } from "../types/cryptoTypes"; + denomSelectionInfoToState, +} from "./withdraw.js"; const logger = new Logger("operations/tip.ts"); diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts @@ -24,9 +24,8 @@ import { RefundState, ReserveRecordStatus, AbortStatus, -} from "../types/dbTypes"; -import { Amounts, AmountJson } from "../util/amounts"; -import { timestampCmp } from "../util/time"; +} from "../db.js"; +import { AmountJson, Amounts, timestampCmp } from "@gnu-taler/taler-util"; import { TransactionsRequest, TransactionsResponse, @@ -36,9 +35,8 @@ import { WithdrawalType, WithdrawalDetails, OrderShortInfo, -} from "../types/transactionsTypes"; +} from "@gnu-taler/taler-util"; import { getFundingPaytoUris } from "./reserves"; -import { TipResponse } from "../types/talerTypes"; /** * Create an event ID from the type and the primary key for the event. diff --git a/packages/taler-wallet-core/src/operations/withdraw-test.ts b/packages/taler-wallet-core/src/operations/withdraw-test.ts @@ -14,10 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { Amounts } from "@gnu-taler/taler-util"; import test from "ava"; +import { DenominationRecord, DenominationStatus } from "../db.js"; import { selectWithdrawalDenominations } from "./withdraw"; -import { Amounts } from "../util/amounts"; -import { DenominationRecord, DenominationStatus } from "../types/dbTypes"; test("withdrawal selection bug repro", (t) => { const amount = { diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, Amounts } from "../util/amounts"; +import { AmountJson, Amounts, parseWithdrawUri, Timestamp } from "@gnu-taler/taler-util"; import { DenominationRecord, Stores, @@ -25,22 +25,22 @@ import { DenominationSelectionInfo, PlanchetRecord, DenomSelectionState, -} from "../types/dbTypes"; + ExchangeRecord, + ExchangeWireInfo, +} from "../db"; import { BankWithdrawDetails, - ExchangeWithdrawDetails, TalerErrorDetails, ExchangeListItem, WithdrawUriInfoResponse, -} from "../types/walletTypes"; +} from "@gnu-taler/taler-util"; import { codecForWithdrawOperationStatusResponse, codecForWithdrawResponse, WithdrawResponse, codecForTalerConfigResponse, -} from "../types/talerTypes"; +} from "@gnu-taler/taler-util"; import { InternalWalletState } from "./state"; -import { parseWithdrawUri } from "../util/taleruri"; import { Logger } from "../util/logging"; import { updateExchangeFromUrl, getExchangeTrust } from "./exchanges"; import { @@ -48,29 +48,115 @@ import { WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, } from "./versions"; -import * as LibtoolVersion from "../util/libtoolVersion"; +import * as LibtoolVersion from "@gnu-taler/taler-util"; import { guardOperationException, makeErrorDetails, OperationFailedError, } from "./errors"; -import { NotificationType } from "../types/notifications"; +import { NotificationType } from "@gnu-taler/taler-util"; import { getTimestampNow, getDurationRemaining, timestampCmp, timestampSubtractDuraction, -} from "../util/time"; +} from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { URL } from "../util/url"; -import { TalerErrorCode } from "../TalerErrorCode"; -import { encodeCrock } from "../crypto/talerCrypto"; +import { TalerErrorCode } from "@gnu-taler/taler-util"; import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries"; -import { compare } from "../util/libtoolVersion"; -import { j2s } from "../util/helpers"; +import { compare } from "@gnu-taler/taler-util"; const logger = new Logger("withdraw.ts"); + +/** + * Information about what will happen when creating a reserve. + * + * Sent to the wallet frontend to be rendered and shown to the user. + */ + interface ExchangeWithdrawDetails { + /** + * Exchange that the reserve will be created at. + */ + exchangeInfo: ExchangeRecord; + + /** + * Filtered wire info to send to the bank. + */ + exchangeWireAccounts: string[]; + + /** + * Selected denominations for withdraw. + */ + selectedDenoms: DenominationSelectionInfo; + + /** + * Fees for withdraw. + */ + withdrawFee: AmountJson; + + /** + * Remaining balance that is too small to be withdrawn. + */ + overhead: AmountJson; + + /** + * Wire fees from the exchange. + */ + wireFees: ExchangeWireInfo; + + /** + * Does the wallet know about an auditor for + * the exchange that the reserve. + */ + isAudited: boolean; + + /** + * Did the user already accept the current terms of service for the exchange? + */ + termsOfServiceAccepted: boolean; + + /** + * The exchange is trusted directly. + */ + isTrusted: boolean; + + /** + * The earliest deposit expiration of the selected coins. + */ + earliestDepositExpiration: Timestamp; + + /** + * Number of currently offered denominations. + */ + numOfferedDenoms: number; + + /** + * Public keys of trusted auditors for the currency we're withdrawing. + */ + trustedAuditorPubs: string[]; + + /** + * Result of checking the wallet's version + * against the exchange's version. + * + * Older exchanges don't return version information. + */ + versionMatch: LibtoolVersion.VersionMatchResult | undefined; + + /** + * Libtool-style version string for the exchange or "unknown" + * for older exchanges. + */ + exchangeVersion: string; + + /** + * Libtool-style version string for the wallet. + */ + walletVersion: string; +} + /** * Check if a denom is withdrawable based on the expiration time * and revocation state. diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts @@ -0,0 +1,290 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Type and schema definitions for pending operations in the wallet. + * + * These are only used internally, and are not part of the public + * interface to the wallet. + */ + +/** + * Imports. + */ +import { TalerErrorDetails, BalancesResponse, Duration, Timestamp } from "@gnu-taler/taler-util"; +import { ReserveRecordStatus } from "./db.js"; +import { RetryInfo } from "./util/retries.js"; + +export enum PendingOperationType { + Bug = "bug", + ExchangeUpdate = "exchange-update", + ExchangeCheckRefresh = "exchange-check-refresh", + Pay = "pay", + ProposalChoice = "proposal-choice", + ProposalDownload = "proposal-download", + Refresh = "refresh", + Reserve = "reserve", + Recoup = "recoup", + RefundQuery = "refund-query", + TipChoice = "tip-choice", + TipPickup = "tip-pickup", + Withdraw = "withdraw", + Deposit = "deposit", +} + +/** + * Information about a pending operation. + */ +export type PendingOperationInfo = PendingOperationInfoCommon & + ( + | PendingBugOperation + | PendingExchangeUpdateOperation + | PendingExchangeCheckRefreshOperation + | PendingPayOperation + | PendingProposalChoiceOperation + | PendingProposalDownloadOperation + | PendingRefreshOperation + | PendingRefundQueryOperation + | PendingReserveOperation + | PendingTipChoiceOperation + | PendingTipPickupOperation + | PendingWithdrawOperation + | PendingRecoupOperation + | PendingDepositOperation + ); + +/** + * The wallet is currently updating information about an exchange. + */ +export interface PendingExchangeUpdateOperation { + type: PendingOperationType.ExchangeUpdate; + stage: ExchangeUpdateOperationStage; + reason: string; + exchangeBaseUrl: string; + lastError: TalerErrorDetails | undefined; +} + +/** + * The wallet should check whether coins from this exchange + * need to be auto-refreshed. + */ +export interface PendingExchangeCheckRefreshOperation { + type: PendingOperationType.ExchangeCheckRefresh; + exchangeBaseUrl: string; +} + +/** + * Some interal error happened in the wallet. This pending operation + * should *only* be reported for problems in the wallet, not when + * a problem with a merchant/exchange/etc. occurs. + */ +export interface PendingBugOperation { + type: PendingOperationType.Bug; + message: string; + details: any; +} + +/** + * Current state of an exchange update operation. + */ +export enum ExchangeUpdateOperationStage { + FetchKeys = "fetch-keys", + FetchWire = "fetch-wire", + FinalizeUpdate = "finalize-update", +} + +export enum ReserveType { + /** + * Manually created. + */ + Manual = "manual", + /** + * Withdrawn from a bank that has "tight" Taler integration + */ + TalerBankWithdraw = "taler-bank-withdraw", +} + +/** + * Status of processing a reserve. + * + * Does *not* include the withdrawal operation that might result + * from this. + */ +export interface PendingReserveOperation { + type: PendingOperationType.Reserve; + retryInfo: RetryInfo | undefined; + stage: ReserveRecordStatus; + timestampCreated: Timestamp; + reserveType: ReserveType; + reservePub: string; + bankWithdrawConfirmUrl?: string; +} + +/** + * Status of an ongoing withdrawal operation. + */ +export interface PendingRefreshOperation { + type: PendingOperationType.Refresh; + lastError?: TalerErrorDetails; + refreshGroupId: string; + finishedPerCoin: boolean[]; + retryInfo: RetryInfo; +} + +/** + * Status of downloading signed contract terms from a merchant. + */ +export interface PendingProposalDownloadOperation { + type: PendingOperationType.ProposalDownload; + merchantBaseUrl: string; + proposalTimestamp: Timestamp; + proposalId: string; + orderId: string; + lastError?: TalerErrorDetails; + retryInfo: RetryInfo; +} + +/** + * User must choose whether to accept or reject the merchant's + * proposed contract terms. + */ +export interface PendingProposalChoiceOperation { + type: PendingOperationType.ProposalChoice; + merchantBaseUrl: string; + proposalTimestamp: Timestamp; + proposalId: string; +} + +/** + * The wallet is picking up a tip that the user has accepted. + */ +export interface PendingTipPickupOperation { + type: PendingOperationType.TipPickup; + tipId: string; + merchantBaseUrl: string; + merchantTipId: string; +} + +/** + * The wallet has been offered a tip, and the user now needs to + * decide whether to accept or reject the tip. + */ +export interface PendingTipChoiceOperation { + type: PendingOperationType.TipChoice; + tipId: string; + merchantBaseUrl: string; + merchantTipId: string; +} + +/** + * The wallet is signing coins and then sending them to + * the merchant. + */ +export interface PendingPayOperation { + type: PendingOperationType.Pay; + proposalId: string; + isReplay: boolean; + retryInfo: RetryInfo; + lastError: TalerErrorDetails | undefined; +} + +/** + * The wallet is querying the merchant about whether any refund + * permissions are available for a purchase. + */ +export interface PendingRefundQueryOperation { + type: PendingOperationType.RefundQuery; + proposalId: string; + retryInfo: RetryInfo; + lastError: TalerErrorDetails | undefined; +} + +export interface PendingRecoupOperation { + type: PendingOperationType.Recoup; + recoupGroupId: string; + retryInfo: RetryInfo; + lastError: TalerErrorDetails | undefined; +} + +/** + * Status of an ongoing withdrawal operation. + */ +export interface PendingWithdrawOperation { + type: PendingOperationType.Withdraw; + lastError: TalerErrorDetails | undefined; + retryInfo: RetryInfo; + withdrawalGroupId: string; + numCoinsWithdrawn: number; + numCoinsTotal: number; +} + +/** + * Status of an ongoing deposit operation. + */ +export interface PendingDepositOperation { + type: PendingOperationType.Deposit; + lastError: TalerErrorDetails | undefined; + retryInfo: RetryInfo; + depositGroupId: string; +} + +/** + * Fields that are present in every pending operation. + */ +export interface PendingOperationInfoCommon { + /** + * Type of the pending operation. + */ + type: PendingOperationType; + + /** + * Set to true if the operation indicates that something is really in progress, + * as opposed to some regular scheduled operation or a permanent failure. + */ + givesLifeness: boolean; + + /** + * Retry info, not available on all pending operations. + * If it is available, it must have the same name. + */ + retryInfo?: RetryInfo; +} + +/** + * Response returned from the pending operations API. + */ +export interface PendingOperationsResponse { + /** + * List of pending operations. + */ + pendingOperations: PendingOperationInfo[]; + + /** + * Current wallet balance, including pending balances. + */ + walletBalance: BalancesResponse; + + /** + * When is the next pending operation due to be re-tried? + */ + nextRetryDelay: Duration; + + /** + * Does this response only include pending operations that + * are due to be executed right now? + */ + onlyDue: boolean; +} diff --git a/packages/taler-wallet-core/src/types/ReserveStatus.ts b/packages/taler-wallet-core/src/types/ReserveStatus.ts @@ -1,57 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { - codecForString, - buildCodecForObject, - codecForList, - Codec, -} from "../util/codec"; -import { AmountString } from "./talerTypes"; -import { - ReserveTransaction, - codecForReserveTransaction, -} from "./ReserveTransaction"; - -/** - * Status of a reserve. - * - * Schema type for the exchange's response to "/reserve/status". - */ -export interface ReserveStatus { - /** - * Balance left in the reserve. - */ - balance: AmountString; - - /** - * Transaction history for the reserve. - */ - history: ReserveTransaction[]; -} - -export const codecForReserveStatus = (): Codec<ReserveStatus> => - buildCodecForObject<ReserveStatus>() - .property("balance", codecForString()) - .property("history", codecForList(codecForReserveTransaction())) - .build("ReserveStatus"); diff --git a/packages/taler-wallet-core/src/types/ReserveTransaction.ts b/packages/taler-wallet-core/src/types/ReserveTransaction.ts @@ -1,253 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type declarations for the exchange's reserve transaction information. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { - codecForString, - buildCodecForObject, - codecForConstString, - buildCodecForUnion, - Codec, - codecForNumber, -} from "../util/codec"; -import { - AmountString, - Base32String, - EddsaSignatureString, - EddsaPublicKeyString, - CoinPublicKeyString, -} from "./talerTypes"; -import { Timestamp, codecForTimestamp } from "../util/time"; - -export enum ReserveTransactionType { - Withdraw = "WITHDRAW", - Credit = "CREDIT", - Recoup = "RECOUP", - Closing = "CLOSING", -} - -export interface ReserveWithdrawTransaction { - type: ReserveTransactionType.Withdraw; - - /** - * Amount withdrawn. - */ - amount: AmountString; - - /** - * Hash of the denomination public key of the coin. - */ - h_denom_pub: Base32String; - - /** - * Hash of the blinded coin to be signed - */ - h_coin_envelope: Base32String; - - /** - * Signature of 'TALER_WithdrawRequestPS' created with the reserves's - * private key. - */ - reserve_sig: EddsaSignatureString; - - /** - * Fee that is charged for withdraw. - */ - withdraw_fee: AmountString; -} - -export interface ReserveCreditTransaction { - type: ReserveTransactionType.Credit; - - /** - * Amount withdrawn. - */ - amount: AmountString; - - /** - * Sender account payto://-URL - */ - sender_account_url: string; - - /** - * Transfer details uniquely identifying the transfer. - */ - wire_reference: number; - - /** - * Timestamp of the incoming wire transfer. - */ - timestamp: Timestamp; -} - -export interface ReserveClosingTransaction { - type: ReserveTransactionType.Closing; - - /** - * Closing balance. - */ - amount: AmountString; - - /** - * Closing fee charged by the exchange. - */ - closing_fee: AmountString; - - /** - * Wire transfer subject. - */ - wtid: string; - - /** - * Hash of the wire account into which the funds were returned to. - */ - h_wire: string; - - /** - * This is a signature over a - * struct TALER_ReserveCloseConfirmationPS with purpose - * TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED. - */ - exchange_sig: EddsaSignatureString; - - /** - * Public key used to create exchange_sig. - */ - exchange_pub: EddsaPublicKeyString; - - /** - * Time when the reserve was closed. - */ - timestamp: Timestamp; -} - -export interface ReserveRecoupTransaction { - type: ReserveTransactionType.Recoup; - - /** - * Amount paid back. - */ - amount: AmountString; - - /** - * This is a signature over - * a struct TALER_PaybackConfirmationPS with purpose - * TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK. - */ - exchange_sig: EddsaSignatureString; - - /** - * Public key used to create exchange_sig. - */ - exchange_pub: EddsaPublicKeyString; - - /** - * Time when the funds were paid back into the reserve. - */ - timestamp: Timestamp; - - /** - * Public key of the coin that was paid back. - */ - coin_pub: CoinPublicKeyString; -} - -/** - * Format of the exchange's transaction history for a reserve. - */ -export type ReserveTransaction = - | ReserveWithdrawTransaction - | ReserveCreditTransaction - | ReserveClosingTransaction - | ReserveRecoupTransaction; - -export const codecForReserveWithdrawTransaction = (): Codec< - ReserveWithdrawTransaction -> => - buildCodecForObject<ReserveWithdrawTransaction>() - .property("amount", codecForString()) - .property("h_coin_envelope", codecForString()) - .property("h_denom_pub", codecForString()) - .property("reserve_sig", codecForString()) - .property("type", codecForConstString(ReserveTransactionType.Withdraw)) - .property("withdraw_fee", codecForString()) - .build("ReserveWithdrawTransaction"); - -export const codecForReserveCreditTransaction = (): Codec< - ReserveCreditTransaction -> => - buildCodecForObject<ReserveCreditTransaction>() - .property("amount", codecForString()) - .property("sender_account_url", codecForString()) - .property("timestamp", codecForTimestamp) - .property("wire_reference", codecForNumber()) - .property("type", codecForConstString(ReserveTransactionType.Credit)) - .build("ReserveCreditTransaction"); - -export const codecForReserveClosingTransaction = (): Codec< - ReserveClosingTransaction -> => - buildCodecForObject<ReserveClosingTransaction>() - .property("amount", codecForString()) - .property("closing_fee", codecForString()) - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("h_wire", codecForString()) - .property("timestamp", codecForTimestamp) - .property("type", codecForConstString(ReserveTransactionType.Closing)) - .property("wtid", codecForString()) - .build("ReserveClosingTransaction"); - -export const codecForReserveRecoupTransaction = (): Codec< - ReserveRecoupTransaction -> => - buildCodecForObject<ReserveRecoupTransaction>() - .property("amount", codecForString()) - .property("coin_pub", codecForString()) - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("timestamp", codecForTimestamp) - .property("type", codecForConstString(ReserveTransactionType.Recoup)) - .build("ReserveRecoupTransaction"); - -export const codecForReserveTransaction = (): Codec<ReserveTransaction> => - buildCodecForUnion<ReserveTransaction>() - .discriminateOn("type") - .alternative( - ReserveTransactionType.Withdraw, - codecForReserveWithdrawTransaction(), - ) - .alternative( - ReserveTransactionType.Closing, - codecForReserveClosingTransaction(), - ) - .alternative( - ReserveTransactionType.Recoup, - codecForReserveRecoupTransaction(), - ) - .alternative( - ReserveTransactionType.Credit, - codecForReserveCreditTransaction(), - ) - .build<ReserveTransaction>("ReserveTransaction"); diff --git a/packages/taler-wallet-core/src/types/backupTypes.ts b/packages/taler-wallet-core/src/types/backupTypes.ts @@ -1,1299 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type declarations for the backup content format. - * - * Contains some redundancy with the other type declarations, - * as the backup schema must remain very stable and should be self-contained. - * - * Future: - * 1. Ghost spends (coin unexpectedly spent by a wallet with shared data) - * 2. Ghost withdrawals (reserve unexpectedly emptied by another wallet with shared data) - * 3. Track losses through re-denomination of payments/refreshes - * 4. (Feature:) Payments to own bank account and P2P-payments need to be backed up - * 5. Track last/next update time, so on restore we need to do less work - * 6. Currency render preferences? - * - * Questions: - * 1. What happens when two backups are merged that have - * the same coin in different refresh groups? - * => Both are added, one will eventually fail - * 2. Should we make more information forgettable? I.e. is - * the coin selection still relevant for a purchase after the coins - * are legally expired? - * => Yes, still needs to be implemented - * 3. What about re-denominations / re-selection of payment coins? - * Is it enough to store a clock value for the selection? - * => Coin derivation should also consider denom pub hash - * - * General considerations / decisions: - * 1. Information about previously occurring errors and - * retries is never backed up. - * 2. The ToS text of an exchange is never backed up. - * 3. Derived information is never backed up (hashed values, public keys - * when we know the private key). - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { Duration, Timestamp } from "../util/time"; - -/** - * Type alias for strings that are to be treated like amounts. - */ -type BackupAmountString = string; - -/** - * A human-recognizable identifier here that is - * reasonable unique and assigned the first time the wallet is - * started/installed, such as: - * - * `${wallet-implementation} ${os} ${hostname} (${short-uid})` - * => e.g. "GNU Taler Android iceking ABC123" - */ -type DeviceIdString = string; - -/** - * Lamport clock timestamp. - */ -export interface ClockStamp { - deviceId: string; - value: number; -} - -/** - * Contract terms JSON. - */ -type RawContractTerms = any; - -/** - * Content of the backup. - * - * The contents of the wallet must be serialized in a deterministic - * way across implementations, so that the normalized backup content - * JSON is identical when the wallet's content is identical. - */ -export interface WalletBackupContentV1 { - /** - * Magic constant to identify that this is a backup content JSON. - */ - schema_id: "gnu-taler-wallet-backup-content"; - - /** - * Version of the schema. - */ - schema_version: 1; - - /** - * Root public key of the wallet. This field is present as - * a sanity check if the backup content JSON is loaded from file. - */ - wallet_root_pub: string; - - /** - * Current device identifier that "owns" the backup. - * - * This identifier allows one wallet to notice when another - * wallet is "alive" and connected to the same sync provider. - */ - current_device_id: DeviceIdString; - - /** - * Monotonically increasing clock of the wallet, - * used to determine causality when merging backups. - * - * Information about other clocks, used to delete - * tombstones in the hopefully rare case that multiple wallets - * are connected to the same sync server. - */ - clocks: { [device_id: string]: number }; - - /** - * Timestamp of the backup. - * - * This timestamp should only be advanced if the content - * of the backup changes. - */ - timestamp: Timestamp; - - /** - * Per-exchange data sorted by exchange master public key. - * - * Sorted by the exchange public key. - */ - exchanges: BackupExchange[]; - - /** - * Grouped refresh sessions. - * - * Sorted by the refresh group ID. - */ - refresh_groups: BackupRefreshGroup[]; - - /** - * Tips. - * - * Sorted by the wallet tip ID. - */ - tips: BackupTip[]; - - /** - * Proposals from merchants. The proposal may - * be deleted as soon as it has been accepted (and thus - * turned into a purchase). - * - * Sorted by the proposal ID. - */ - proposals: BackupProposal[]; - - /** - * Accepted purchases. - * - * Sorted by the proposal ID. - */ - purchases: BackupPurchase[]; - - /** - * All backup providers. - * - * Sorted by the provider base URL. - */ - backup_providers: BackupBackupProvider[]; - - /** - * Recoup groups. - */ - recoup_groups: BackupRecoupGroup[]; - - /** - * Tombstones for deleting purchases. - */ - purchase_tombstones: { - /** - * Clock when the purchase was deleted - */ - clock_deleted: ClockStamp; - - /** - * Proposal ID identifying the purchase. - */ - proposal_id: string; - }[]; - - /** - * Trusted auditors, either for official (3 letter) or local (4-12 letter) - * currencies. - * - * Auditors are sorted by their canonicalized base URL. - */ - trusted_auditors: { [currency: string]: BackupTrustAuditor[] }; - - /** - * Trusted exchange. Only applicable for local currencies (4-12 letter currency code). - * - * Exchanges are sorted by their canonicalized base URL. - */ - trusted_exchanges: { [currency: string]: BackupTrustExchange[] }; - - /** - * Interning table for forgettable values of contract terms. - * - * Used to reduce storage space, as many forgettable items (product image, - * addresses, etc.) might be shared among many contract terms. - */ - intern_table: { [hash: string]: any }; - - /** - * Permanent error reports. - */ - error_reports: BackupErrorReport[]; -} - -/** - * Detailed error report. - * - * For auditor-relevant reports with attached cryptographic proof, - * the error report also should contain the submission status to - * the auditor(s). - */ -interface BackupErrorReport { - // FIXME: specify! -} - -/** - * Trust declaration for an auditor. - * - * The trust applies based on the public key of - * the auditor, irrespective of what base URL the exchange - * is referencing. - */ -export interface BackupTrustAuditor { - /** - * Base URL of the auditor. - */ - auditor_base_url: string; - - /** - * Public key of the auditor. - */ - auditor_pub: string; - - /** - * Clock when the auditor trust has been added. - * - * Can be undefined if this entry represents a removal delta - * from the wallet's defaults. - */ - clock_added?: ClockStamp; - - /** - * Clock for when the auditor trust has been removed. - */ - clock_removed?: ClockStamp; -} - -/** - * Trust declaration for an exchange. - * - * The trust only applies for the combination of base URL - * and public key. If the master public key changes while the base - * URL stays the same, the exchange has to be re-added by a wallet update - * or by the user. - */ -export interface BackupTrustExchange { - /** - * Canonicalized exchange base URL. - */ - exchange_base_url: string; - - /** - * Master public key of the exchange. - */ - exchange_master_pub: string; - - /** - * Clock when the exchange trust has been added. - * - * Can be undefined if this entry represents a removal delta - * from the wallet's defaults. - */ - clock_added?: ClockStamp; - - /** - * Clock for when the exchange trust has been removed. - */ - clock_removed?: ClockStamp; -} - -export class BackupBackupProviderTerms { - /** - * Last known supported protocol version. - */ - supported_protocol_version: string; - - /** - * Last known annual fee. - */ - annual_fee: BackupAmountString; - - /** - * Last known storage limit. - */ - storage_limit_in_megabytes: number; -} - -/** - * Backup information about one backup storage provider. - */ -export class BackupBackupProvider { - /** - * Canonicalized base URL of the provider. - */ - base_url: string; - - /** - * Last known terms. Might be unavailable in some situations, such - * as directly after restoring form a backup recovery document. - */ - terms?: BackupBackupProviderTerms; - - /** - * Proposal IDs for payments to this provider. - */ - pay_proposal_ids: string[]; -} - -/** - * Status of recoup operations that were grouped together. - * - * The remaining amount of the corresponding coins must be set to - * zero when the recoup group is created/imported. - */ -export interface BackupRecoupGroup { - /** - * Unique identifier for the recoup group record. - */ - recoup_group_id: string; - - /** - * Timestamp when the recoup was started. - */ - timestamp_created: Timestamp; - - timestamp_finish?: Timestamp; - finish_clock?: Timestamp; - finish_is_failure?: boolean; - - /** - * Information about each coin being recouped. - */ - coins: { - coin_pub: string; - recoup_finished: boolean; - old_amount: BackupAmountString; - }[]; -} - -/** - * Types of coin sources. - */ -export enum BackupCoinSourceType { - Withdraw = "withdraw", - Refresh = "refresh", - Tip = "tip", -} - -/** - * Metadata about a coin obtained via withdrawing. - */ -export interface BackupWithdrawCoinSource { - type: BackupCoinSourceType.Withdraw; - - /** - * Can be the empty string for orphaned coins. - */ - withdrawal_group_id: string; - - /** - * Index of the coin in the withdrawal session. - */ - coin_index: number; - - /** - * Reserve public key for the reserve we got this coin from. - */ - reserve_pub: string; -} - -/** - * Metadata about a coin obtained from refreshing. - * - * FIXME: Currently does not link to the refreshGroupId because - * the wallet DB doesn't do this. Not really necessary, - * but would be more consistent. - */ -export interface BackupRefreshCoinSource { - type: BackupCoinSourceType.Refresh; - - /** - * Public key of the coin that was refreshed into this coin. - */ - old_coin_pub: string; -} - -/** - * Metadata about a coin obtained from a tip. - */ -export interface BackupTipCoinSource { - type: BackupCoinSourceType.Tip; - - /** - * Wallet's identifier for the tip that this coin - * originates from. - */ - wallet_tip_id: string; - - /** - * Index in the tip planchets of the tip. - */ - coin_index: number; -} - -/** - * Metadata about a coin depending on the origin. - */ -export type BackupCoinSource = - | BackupWithdrawCoinSource - | BackupRefreshCoinSource - | BackupTipCoinSource; - -/** - * Backup information about a coin. - * - * (Always part of a BackupExchange/BackupDenom) - */ -export interface BackupCoin { - /** - * Where did the coin come from? Used for recouping coins. - */ - coin_source: BackupCoinSource; - - /** - * Private key to authorize operations on the coin. - */ - coin_priv: string; - - /** - * Unblinded signature by the exchange. - */ - denom_sig: string; - - /** - * Amount that's left on the coin. - */ - current_amount: BackupAmountString; - - /** - * Blinding key used when withdrawing the coin. - * Potentionally used again during payback. - */ - blinding_key: string; - - /** - * Does the wallet think that the coin is still fresh? - * - * Note that even if a fresh coin is imported, it should still - * be refreshed in most situations. - */ - fresh: boolean; - - /** - * Clock for the last update to current_amount/fresh. - */ - last_clock?: ClockStamp; -} - -/** - * Status of a tip we got from a merchant. - */ -export interface BackupTip { - /** - * Tip ID chosen by the wallet. - */ - wallet_tip_id: string; - - /** - * The merchant's identifier for this tip. - */ - merchant_tip_id: string; - - /** - * Secret seed used for the tipping planchets. - */ - secret_seed: string; - - /** - * Has the user accepted the tip? Only after the tip has been accepted coins - * withdrawn from the tip may be used. - */ - timestamp_accepted: Timestamp | undefined; - - /** - * When was the tip first scanned by the wallet? - */ - timestamp_created: Timestamp; - - timestamp_finished?: Timestamp; - finish_clock?: ClockStamp; - finish_is_failure?: boolean; - - /** - * The tipped amount. - */ - tip_amount_raw: BackupAmountString; - - /** - * Timestamp, the tip can't be picked up anymore after this deadline. - */ - timestamp_expiration: Timestamp; - - /** - * The exchange that will sign our coins, chosen by the merchant. - */ - exchange_base_url: string; - - /** - * Base URL of the merchant that is giving us the tip. - */ - merchant_base_url: string; - - /** - * Selected denominations. Determines the effective tip amount. - */ - selected_denoms: BackupDenomSel; - - selected_denoms_clock?: ClockStamp; -} - -/** - * Reasons for why a coin is being refreshed. - */ -export enum BackupRefreshReason { - Manual = "manual", - Pay = "pay", - Refund = "refund", - AbortPay = "abort-pay", - Recoup = "recoup", - BackupRestored = "backup-restored", - Scheduled = "scheduled", -} - -/** - * Information about one refresh session, always part - * of a refresh group. - * - * (Public key of the old coin is stored in the refresh group.) - */ -export interface BackupRefreshSession { - /** - * Hashed denominations of the newly requested coins. - */ - new_denoms: BackupDenomSel; - - /** - * Seed used to derive the planchets and - * transfer private keys for this refresh session. - */ - session_secret_seed: string; - - /** - * The no-reveal-index after we've done the melting. - */ - noreveal_index?: number; -} - -/** - * Refresh session for one coin inside a refresh group. - */ -export interface BackupRefreshOldCoin { - /** - * Public key of the old coin, - */ - coin_pub: string; - - /** - * Requested amount to refresh. Must be subtracted from the coin's remaining - * amount as soon as the coin is added to the refresh group. - */ - input_amount: BackupAmountString; - - /** - * Estimated output (may change if it takes a long time to create the - * actual session). - */ - estimated_output_amount: BackupAmountString; - - /** - * Did the refresh session finish (or was it unnecessary/impossible to create - * one) - */ - finished: boolean; - - /** - * Refresh session (if created) or undefined it not created yet. - */ - refresh_session: BackupRefreshSession | undefined; -} - -/** - * Information about one refresh group. - * - * May span more than one exchange, but typically doesn't - */ -export interface BackupRefreshGroup { - refresh_group_id: string; - - reason: BackupRefreshReason; - - /** - * Details per old coin. - */ - old_coins: BackupRefreshOldCoin[]; - - timestamp_created: Timestamp; - - timestamp_finish?: Timestamp; - finish_clock?: ClockStamp; - finish_is_failure?: boolean; -} - -/** - * Backup information for a withdrawal group. - * - * Always part of a BackupReserve. - */ -export interface BackupWithdrawalGroup { - withdrawal_group_id: string; - - /** - * Secret seed to derive the planchets. - */ - secret_seed: string; - - /** - * When was the withdrawal operation started started? - * Timestamp in milliseconds. - */ - timestamp_created: Timestamp; - - timestamp_finish?: Timestamp; - finish_clock?: ClockStamp; - finish_is_failure?: boolean; - - /** - * Amount including fees (i.e. the amount subtracted from the - * reserve to withdraw all coins in this withdrawal session). - * - * Note that this *includes* the amount remaining in the reserve - * that is too small to be withdrawn, and thus can't be derived - * from selectedDenoms. - */ - raw_withdrawal_amount: BackupAmountString; - - /** - * Multiset of denominations selected for withdrawal. - */ - selected_denoms: BackupDenomSel; - - selected_denoms_clock?: ClockStamp; -} - -export enum BackupRefundState { - Failed = "failed", - Applied = "applied", - Pending = "pending", -} - -/** - * Common information about a refund. - */ -export interface BackupRefundItemCommon { - /** - * Execution time as claimed by the merchant - */ - execution_time: Timestamp; - - /** - * Time when the wallet became aware of the refund. - */ - obtained_time: Timestamp; - - /** - * Amount refunded for the coin. - */ - refund_amount: BackupAmountString; - - /** - * Coin being refunded. - */ - coin_pub: string; - - /** - * The refund transaction ID for the refund. - */ - rtransaction_id: number; - - /** - * Upper bound on the refresh cost incurred by - * applying this refund. - * - * Might be lower in practice when two refunds on the same - * coin are refreshed in the same refresh operation. - * - * Used to display fees, and stored since it's expensive to recompute - * accurately. - */ - total_refresh_cost_bound: BackupAmountString; - - last_clock?: ClockStamp; -} - -/** - * Failed refund, either because the merchant did - * something wrong or it expired. - */ -export interface BackupRefundFailedItem extends BackupRefundItemCommon { - type: BackupRefundState.Failed; -} - -export interface BackupRefundPendingItem extends BackupRefundItemCommon { - type: BackupRefundState.Pending; -} - -export interface BackupRefundAppliedItem extends BackupRefundItemCommon { - type: BackupRefundState.Applied; -} - -/** - * State of one refund from the merchant, maintained by the wallet. - */ -export type BackupRefundItem = - | BackupRefundFailedItem - | BackupRefundPendingItem - | BackupRefundAppliedItem; - -export interface BackupPurchase { - /** - * Proposal ID for this purchase. Uniquely identifies the - * purchase and the proposal. - */ - proposal_id: string; - - /** - * Contract terms we got from the merchant. - */ - contract_terms_raw: RawContractTerms; - - /** - * Signature on the contract terms. - */ - merchant_sig: string; - - /** - * Private key for the nonce. Might eventually be used - * to prove ownership of the contract. - */ - nonce_priv: string; - - pay_coins: { - /** - * Public keys of the coins that were selected. - */ - coin_pub: string; - - /** - * Amount that each coin contributes. - */ - contribution: BackupAmountString; - }[]; - - /** - * Clock when the pay coin selection was made/updated. - */ - pay_coins_clock?: ClockStamp; - - /** - * Total cost initially shown to the user. - * - * This includes the amount taken by the merchant, fees (wire/deposit) contributed - * by the customer, refreshing fees, fees for withdraw-after-refresh and "trimmings" - * of coins that are too small to spend. - * - * Note that in rare situations, this cost might not be accurate (e.g. - * when the payment or refresh gets re-denominated). - * We might show adjustments to this later, but currently we don't do so. - */ - total_pay_cost: BackupAmountString; - - /** - * Timestamp of the first time that sending a payment to the merchant - * for this purchase was successful. - */ - timestamp_first_successful_pay: Timestamp | undefined; - - /** - * Signature by the merchant confirming the payment. - */ - merchant_pay_sig: string | undefined; - - /** - * When was the purchase made? - * Refers to the time that the user accepted. - */ - timestamp_accept: Timestamp; - - /** - * Pending refunds for the purchase. A refund is pending - * when the merchant reports a transient error from the exchange. - */ - refunds: BackupRefundItem[]; - - /** - * Is the purchase considered defunct (either during payment - * or during abort if abort_status is set). - */ - defunct?: boolean; - - /** - * Clock for last update to defunct status. - */ - defunct_clock?: ClockStamp; - - /** - * Abort status of the payment. - */ - abort_status?: "abort-refund" | "abort-finished"; - - /** - * Continue querying the refund status until this deadline has expired. - */ - auto_refund_deadline: Timestamp | undefined; -} - -/** - * Info about one denomination in the backup. - * - * Note that the wallet only backs up validated denominations. - */ -export interface BackupDenomination { - /** - * Value of one coin of the denomination. - */ - value: BackupAmountString; - - /** - * The denomination public key. - */ - denom_pub: string; - - /** - * Fee for withdrawing. - */ - fee_withdraw: BackupAmountString; - - /** - * Fee for depositing. - */ - fee_deposit: BackupAmountString; - - /** - * Fee for refreshing. - */ - fee_refresh: BackupAmountString; - - /** - * Fee for refunding. - */ - fee_refund: BackupAmountString; - - /** - * Validity start date of the denomination. - */ - stamp_start: Timestamp; - - /** - * Date after which the currency can't be withdrawn anymore. - */ - stamp_expire_withdraw: Timestamp; - - /** - * Date after the denomination officially doesn't exist anymore. - */ - stamp_expire_legal: Timestamp; - - /** - * Data after which coins of this denomination can't be deposited anymore. - */ - stamp_expire_deposit: Timestamp; - - /** - * Signature by the exchange's master key over the denomination - * information. - */ - master_sig: string; - - /** - * Was this denomination still offered by the exchange the last time - * we checked? - * Only false when the exchange redacts a previously published denomination. - */ - is_offered: boolean; - - /** - * Did the exchange revoke the denomination? - * When this field is set to true in the database, the same transaction - * should also mark all affected coins as revoked. - */ - is_revoked: boolean; - - /** - * Coins of this denomination. - */ - coins: BackupCoin[]; -} - -/** - * Denomination selection. - */ -export type BackupDenomSel = { - denom_pub_hash: string; - count: number; -}[]; - -export interface BackupReserve { - /** - * The reserve private key. - */ - reserve_priv: string; - - /** - * Time when the reserve was created. - */ - timestamp_created: Timestamp; - - /** - * Timestamp of the last observed activity. - * - * Used to compute when to give up querying the exchange. - */ - timestamp_last_activity: Timestamp; - - /** - * Timestamp of when the reserve closed. - * - * Note that the last activity can be after the closing time - * due to recouping. - */ - timestamp_closed?: Timestamp; - - /** - * Wire information (as payto URI) for the bank account that - * transfered funds for this reserve. - */ - sender_wire?: string; - - /** - * Amount that was sent by the user to fund the reserve. - */ - instructed_amount: BackupAmountString; - - /** - * Extra state for when this is a withdrawal involving - * a Taler-integrated bank. - */ - bank_info?: { - /** - * Status URL that the wallet will use to query the status - * of the Taler withdrawal operation on the bank's side. - */ - status_url: string; - - /** - * URL that the user should be instructed to navigate to - * in order to confirm the transfer (or show instructions/help - * on how to do that at a PoS terminal). - */ - confirm_url?: string; - - /** - * Exchange payto URI that the bank will use to fund the reserve. - */ - exchange_payto_uri: string; - - /** - * Time when the information about this reserve was posted to the bank. - */ - timestamp_reserve_info_posted: Timestamp | undefined; - - /** - * Time when the reserve was confirmed by the bank. - * - * Set to undefined if not confirmed yet. - */ - timestamp_bank_confirmed: Timestamp | undefined; - }; - - /** - * Pre-allocated withdrawal group ID that will be - * used for the first withdrawal. - * - * (Already created so it can be referenced in the transactions list - * before it really exists, as there'll be an entry for the withdrawal - * even before the withdrawal group really has been created). - */ - initial_withdrawal_group_id: string; - - /** - * Denominations selected for the initial withdrawal. - * Stored here to show costs before withdrawal has begun. - */ - initial_selected_denoms: BackupDenomSel; - - /** - * Groups of withdrawal operations for this reserve. Typically just one. - */ - withdrawal_groups: BackupWithdrawalGroup[]; - - defective?: boolean; - defective_clock?: ClockStamp; -} - -/** - * Wire fee for one wire payment target type as stored in the - * wallet's database. - * - * (Flattened to a list to make the declaration simpler). - */ -export interface BackupExchangeWireFee { - wire_type: string; - - /** - * Fee for wire transfers. - */ - wire_fee: string; - - /** - * Fees to close and refund a reserve. - */ - closing_fee: string; - - /** - * Start date of the fee. - */ - start_stamp: Timestamp; - - /** - * End date of the fee. - */ - end_stamp: Timestamp; - - /** - * Signature made by the exchange master key. - */ - sig: string; -} - -/** - * Structure of one exchange signing key in the /keys response. - */ -export class BackupExchangeSignKey { - stamp_start: Timestamp; - stamp_expire: Timestamp; - stamp_end: Timestamp; - key: string; - master_sig: string; -} - -/** - * Signature by the auditor that a particular denomination key is audited. - */ -export class AuditorDenomSig { - /** - * Denomination public key's hash. - */ - denom_pub_h: string; - - /** - * The signature. - */ - auditor_sig: string; -} - -/** - * Auditor information as given by the exchange in /keys. - */ -export class BackupExchangeAuditor { - /** - * Auditor's public key. - */ - auditor_pub: string; - - /** - * Base URL of the auditor. - */ - auditor_url: string; - - /** - * List of signatures for denominations by the auditor. - */ - denomination_keys: AuditorDenomSig[]; -} - -/** - * Backup information about an exchange. - */ -export interface BackupExchange { - /** - * Canonicalized base url of the exchange. - */ - base_url: string; - - /** - * Master public key of the exchange. - */ - master_public_key: string; - - /** - * Auditors (partially) auditing the exchange. - */ - auditors: BackupExchangeAuditor[]; - - /** - * Currency that the exchange offers. - */ - currency: string; - - /** - * Denominations offered by the exchange. - */ - denominations: BackupDenomination[]; - - /** - * Reserves at the exchange. - */ - reserves: BackupReserve[]; - - /** - * Last observed protocol version. - */ - protocol_version: string; - - /** - * Closing delay of reserves. - */ - reserve_closing_delay: Duration; - - /** - * Signing keys we got from the exchange, can also contain - * older signing keys that are not returned by /keys anymore. - */ - signing_keys: BackupExchangeSignKey[]; - - wire_fees: BackupExchangeWireFee[]; - - /** - * Bank accounts offered by the exchange; - */ - accounts: { - payto_uri: string; - master_sig: string; - }[]; - - /** - * ETag for last terms of service download. - */ - tos_etag_last: string | undefined; - - /** - * ETag for last terms of service download. - */ - tos_etag_accepted: string | undefined; - - /** - * Clock value of the last update. - */ - last_clock?: ClockStamp; - - /** - * Should this exchange be considered defective? - */ - defective?: boolean; - - defective_clock?: ClockStamp; -} - -export enum BackupProposalStatus { - /** - * Proposed (and either downloaded or not, - * depending on whether contract terms are present), - * but the user needs to accept/reject it. - */ - Proposed = "proposed", - /** - * The user has rejected the proposal. - */ - Refused = "refused", - /** - * Downloading or processing the proposal has failed permanently. - * - * FIXME: Should this be modeled as a "misbehavior report" instead? - */ - PermanentlyFailed = "permanently-failed", - /** - * Downloaded proposal was detected as a re-purchase. - */ - Repurchase = "repurchase", -} - -/** - * Proposal by a merchant. - */ -export interface BackupProposal { - /** - * Base URL of the merchant that proposed the purchase. - */ - merchant_base_url: string; - - /** - * Downloaded data from the merchant. - */ - contract_terms_raw?: RawContractTerms; - - /** - * Signature on the contract terms. - * - * Must be present if contract_terms_raw is present. - */ - merchant_sig?: string; - - /** - * Unique ID when the order is stored in the wallet DB. - */ - proposal_id: string; - - /** - * Merchant-assigned order ID of the proposal. - */ - order_id: string; - - /** - * Timestamp of when the record - * was created. - */ - timestamp: Timestamp; - - /** - * Private key for the nonce. - */ - nonce_priv: string; - - /** - * Claim token initially given by the merchant. - */ - claim_token: string | undefined; - - /** - * Status of the proposal. - */ - proposal_status: BackupProposalStatus; - - proposal_status_clock?: ClockStamp; - - /** - * Proposal that this one got "redirected" to as part of - * the repurchase detection. - */ - repurchase_proposal_id: string | undefined; - - /** - * Session ID we got when downloading the contract. - */ - download_session_id?: string; -} diff --git a/packages/taler-wallet-core/src/types/cryptoTypes.ts b/packages/taler-wallet-core/src/types/cryptoTypes.ts @@ -1,141 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Types used by the wallet crypto worker. - * - * These types are defined in a separate file make tree shaking easier, since - * some components use these types (via RPC) but do not depend on the wallet - * code directly. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { AmountJson } from "../util/amounts"; - -export interface RefreshNewDenomInfo { - count: number; - value: AmountJson; - feeWithdraw: AmountJson; - denomPub: string; -} - -/** - * Request to derive a refresh session from the refresh session - * secret seed. - */ -export interface DeriveRefreshSessionRequest { - sessionSecretSeed: string; - kappa: number; - meltCoinPub: string; - meltCoinPriv: string; - meltCoinDenomPubHash: string; - newCoinDenoms: RefreshNewDenomInfo[]; - feeRefresh: AmountJson; -} - -/** - * - */ -export interface DerivedRefreshSession { - /** - * Public key that's being melted in this session. - */ - meltCoinPub: string; - - /** - * Signature to confirm the melting. - */ - confirmSig: string; - - /** - * Planchets for each cut-and-choose instance. - */ - planchetsForGammas: { - /** - * Public key for the coin. - */ - publicKey: string; - - /** - * Private key for the coin. - */ - privateKey: string; - - /** - * Blinded public key. - */ - coinEv: string; - - /** - * Hash of the blinded public key. - */ - coinEvHash: string; - - /** - * Blinding key used. - */ - blindingKey: string; - }[][]; - - /** - * The transfer keys, kappa of them. - */ - transferPubs: string[]; - - /** - * Private keys for the transfer public keys. - */ - transferPrivs: string[]; - - /** - * Hash of the session. - */ - hash: string; - - /** - * Exact value that is being melted. - */ - meltValueWithFee: AmountJson; -} - -export interface DeriveTipRequest { - secretSeed: string; - denomPub: string; - planchetIndex: number; -} - -/** - * Tipping planchet stored in the database. - */ -export interface DerivedTipPlanchet { - blindingKey: string; - coinEv: string; - coinEvHash: string; - coinPriv: string; - coinPub: string; -} - -export interface SignTrackTransactionRequest { - contractTermsHash: string; - wireHash: string; - coinPub: string; - merchantPriv: string; - merchantPub: string; -} diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts @@ -1,1775 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2018-2020 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Types for records stored in the wallet's database. - * - * Types for the objects in the database should end in "-Record". - */ - -/** - * Imports. - */ -import { AmountJson } from "../util/amounts"; -import { - Auditor, - CoinDepositPermission, - ExchangeSignKeyJson, - MerchantInfo, - Product, - InternationalizedString, - AmountString, - ContractTerms, -} from "./talerTypes"; - -import { Index, Store } from "../util/query"; -import { TalerErrorDetails, RefreshReason } from "./walletTypes"; -import { ReserveTransaction } from "./ReserveTransaction"; -import { Timestamp, Duration } from "../util/time"; -import { IDBKeyPath } from "@gnu-taler/idb-bridge"; -import { RetryInfo } from "../util/retries"; -import { PayCoinSelection } from "../util/coinSelection"; - -export enum ReserveRecordStatus { - /** - * Reserve must be registered with the bank. - */ - REGISTERING_BANK = "registering-bank", - - /** - * We've registered reserve's information with the bank - * and are now waiting for the user to confirm the withdraw - * with the bank (typically 2nd factor auth). - */ - WAIT_CONFIRM_BANK = "wait-confirm-bank", - - /** - * Querying reserve status with the exchange. - */ - QUERYING_STATUS = "querying-status", - - /** - * The corresponding withdraw record has been created. - * No further processing is done, unless explicitly requested - * by the user. - */ - DORMANT = "dormant", - - /** - * The bank aborted the withdrawal. - */ - BANK_ABORTED = "bank-aborted", -} - -export interface ReserveBankInfo { - /** - * Status URL that the wallet will use to query the status - * of the Taler withdrawal operation on the bank's side. - */ - statusUrl: string; - - confirmUrl?: string; - - /** - * Exchange payto URI that the bank will use to fund the reserve. - */ - exchangePaytoUri: string; -} - -/** - * A reserve record as stored in the wallet's database. - */ -export interface ReserveRecord { - /** - * The reserve public key. - */ - reservePub: string; - - /** - * The reserve private key. - */ - reservePriv: string; - - /** - * The exchange base URL. - */ - exchangeBaseUrl: string; - - /** - * Currency of the reserve. - */ - currency: string; - - /** - * Time when the reserve was created. - */ - timestampCreated: Timestamp; - - /** - * Time when the information about this reserve was posted to the bank. - * - * Only applies if bankWithdrawStatusUrl is defined. - * - * Set to 0 if that hasn't happened yet. - */ - timestampReserveInfoPosted: Timestamp | undefined; - - /** - * Time when the reserve was confirmed by the bank. - * - * Set to undefined if not confirmed yet. - */ - timestampBankConfirmed: Timestamp | undefined; - - /** - * Wire information (as payto URI) for the bank account that - * transfered funds for this reserve. - */ - senderWire?: string; - - /** - * Amount that was sent by the user to fund the reserve. - */ - instructedAmount: AmountJson; - - /** - * Extra state for when this is a withdrawal involving - * a Taler-integrated bank. - */ - bankInfo?: ReserveBankInfo; - - initialWithdrawalGroupId: string; - - /** - * Did we start the first withdrawal for this reserve? - * - * We only report a pending withdrawal for the reserve before - * the first withdrawal has started. - */ - initialWithdrawalStarted: boolean; - - /** - * Initial denomination selection, stored here so that - * we can show this information in the transactions/balances - * before we have a withdrawal group. - */ - initialDenomSel: DenomSelectionState; - - reserveStatus: ReserveRecordStatus; - - /** - * Was a reserve query requested? If so, query again instead - * of going into dormant status. - */ - requestedQuery: boolean; - - /** - * Time of the last successful status query. - */ - lastSuccessfulStatusQuery: Timestamp | undefined; - - /** - * Retry info. This field is present even if no retry is scheduled, - * because we need it to be present for the index on the object store - * to work. - */ - retryInfo: RetryInfo; - - /** - * Last error that happened in a reserve operation - * (either talking to the bank or the exchange). - */ - lastError: TalerErrorDetails | undefined; -} - -/** - * Auditor record as stored with currencies in the exchange database. - */ -export interface AuditorRecord { - /** - * Base url of the auditor. - */ - baseUrl: string; - - /** - * Public signing key of the auditor. - */ - auditorPub: string; - - /** - * Time when the auditing expires. - */ - expirationStamp: number; -} - -/** - * Exchange for currencies as stored in the wallet's currency - * information database. - */ -export interface ExchangeForCurrencyRecord { - /** - * FIXME: unused? - */ - exchangeMasterPub: string; - - /** - * Base URL of the exchange. - */ - exchangeBaseUrl: string; -} - -/** - * Information about a currency as displayed in the wallet's database. - */ -export interface CurrencyRecord { - /** - * Name of the currency. - */ - name: string; - - /** - * Number of fractional digits to show when rendering the currency. - */ - fractionalDigits: number; - - /** - * Auditors that the wallet trusts for this currency. - */ - auditors: AuditorRecord[]; - - /** - * Exchanges that the wallet trusts for this currency. - */ - exchanges: ExchangeForCurrencyRecord[]; -} - -/** - * Status of a denomination. - */ -export enum DenominationStatus { - /** - * Verification was delayed. - */ - Unverified = "unverified", - /** - * Verified as valid. - */ - VerifiedGood = "verified-good", - /** - * Verified as invalid. - */ - VerifiedBad = "verified-bad", -} - -/** - * Denomination record as stored in the wallet's database. - */ -export interface DenominationRecord { - /** - * Value of one coin of the denomination. - */ - value: AmountJson; - - /** - * The denomination public key. - */ - denomPub: string; - - /** - * Hash of the denomination public key. - * Stored in the database for faster lookups. - */ - denomPubHash: string; - - /** - * Fee for withdrawing. - */ - feeWithdraw: AmountJson; - - /** - * Fee for depositing. - */ - feeDeposit: AmountJson; - - /** - * Fee for refreshing. - */ - feeRefresh: AmountJson; - - /** - * Fee for refunding. - */ - feeRefund: AmountJson; - - /** - * Validity start date of the denomination. - */ - stampStart: Timestamp; - - /** - * Date after which the currency can't be withdrawn anymore. - */ - stampExpireWithdraw: Timestamp; - - /** - * Date after the denomination officially doesn't exist anymore. - */ - stampExpireLegal: Timestamp; - - /** - * Data after which coins of this denomination can't be deposited anymore. - */ - stampExpireDeposit: Timestamp; - - /** - * Signature by the exchange's master key over the denomination - * information. - */ - masterSig: string; - - /** - * Did we verify the signature on the denomination? - * - * FIXME: Rename to "verificationStatus"? - */ - status: DenominationStatus; - - /** - * Was this denomination still offered by the exchange the last time - * we checked? - * Only false when the exchange redacts a previously published denomination. - */ - isOffered: boolean; - - /** - * Did the exchange revoke the denomination? - * When this field is set to true in the database, the same transaction - * should also mark all affected coins as revoked. - */ - isRevoked: boolean; - - /** - * Base URL of the exchange. - */ - exchangeBaseUrl: string; -} - -/** - * Details about the exchange that we only know after - * querying /keys and /wire. - */ -export interface ExchangeDetails { - /** - * Master public key of the exchange. - */ - masterPublicKey: string; - - /** - * Auditors (partially) auditing the exchange. - */ - auditors: Auditor[]; - - /** - * Currency that the exchange offers. - */ - currency: string; - - /** - * Last observed protocol version. - */ - protocolVersion: string; - - reserveClosingDelay: Duration; - - /** - * Signing keys we got from the exchange, can also contain - * older signing keys that are not returned by /keys anymore. - */ - signingKeys: ExchangeSignKeyJson[]; - - /** - * Timestamp for last update. - */ - lastUpdateTime: Timestamp; - - /** - * When should we next update the information about the exchange? - */ - nextUpdateTime: Timestamp; -} - -export enum ExchangeUpdateStatus { - FetchKeys = "fetch-keys", - FetchWire = "fetch-wire", - FetchTerms = "fetch-terms", - FinalizeUpdate = "finalize-update", - Finished = "finished", -} - -export interface ExchangeBankAccount { - payto_uri: string; - master_sig: string; -} - -export interface ExchangeWireInfo { - feesForType: { [wireMethod: string]: WireFee[] }; - accounts: ExchangeBankAccount[]; -} - -export enum ExchangeUpdateReason { - Initial = "initial", - Forced = "forced", - Scheduled = "scheduled", -} - -/** - * Exchange record as stored in the wallet's database. - */ -export interface ExchangeRecord { - /** - * Base url of the exchange. - */ - baseUrl: string; - - /** - * Did we finish adding the exchange? - */ - addComplete: boolean; - - /** - * Is this a permanent or temporary exchange record? - */ - permanent: boolean; - - /** - * Was the exchange added as a built-in exchange? - */ - builtIn: boolean; - - /** - * Details, once known. - */ - details: ExchangeDetails | undefined; - - /** - * Mapping from wire method type to the wire fee. - */ - wireInfo: ExchangeWireInfo | undefined; - - /** - * Terms of service text or undefined if not downloaded yet. - * - * This is just used as a cache of the last downloaded ToS. - */ - termsOfServiceText: string | undefined; - - /** - * ETag for last terms of service download. - */ - termsOfServiceLastEtag: string | undefined; - - /** - * ETag for last terms of service download. - */ - termsOfServiceAcceptedEtag: string | undefined; - - /** - * Time when the update to the exchange has been started or - * undefined if no update is in progress. - */ - updateStarted: Timestamp | undefined; - - /** - * Status of updating the info about the exchange. - */ - updateStatus: ExchangeUpdateStatus; - - updateReason?: ExchangeUpdateReason; - - lastError?: TalerErrorDetails; - - /** - * Retry status for fetching updated information about the exchange. - */ - retryInfo: RetryInfo; - - /** - * Next time that we should check if coins need to be refreshed. - * - * Updated whenever the exchange's denominations are updated or when - * the refresh check has been done. - */ - nextRefreshCheck?: Timestamp; -} - -/** - * A coin that isn't yet signed by an exchange. - */ -export interface PlanchetRecord { - /** - * Public key of the coin. - */ - coinPub: string; - - /** - * Private key of the coin. - */ - coinPriv: string; - - /** - * Withdrawal group that this planchet belongs to - * (or the empty string). - */ - withdrawalGroupId: string; - - /** - * Index within the withdrawal group (or -1). - */ - coinIdx: number; - - withdrawalDone: boolean; - - lastError: TalerErrorDetails | undefined; - - /** - * Public key of the reserve that this planchet - * is being withdrawn from. - * - * Can be the empty string (non-null/undefined for DB indexing) - * if this is a tipping reserve. - */ - reservePub: string; - - denomPubHash: string; - - denomPub: string; - - blindingKey: string; - - withdrawSig: string; - - coinEv: string; - - coinEvHash: string; - - coinValue: AmountJson; - - isFromTip: boolean; -} - -/** - * Planchet for a coin during refrehs. - */ -export interface RefreshPlanchet { - /** - * Public key for the coin. - */ - publicKey: string; - - /** - * Private key for the coin. - */ - privateKey: string; - - /** - * Blinded public key. - */ - coinEv: string; - - coinEvHash: string; - - /** - * Blinding key used. - */ - blindingKey: string; -} - -/** - * Status of a coin. - */ -export enum CoinStatus { - /** - * Withdrawn and never shown to anybody. - */ - Fresh = "fresh", - /** - * A coin that has been spent and refreshed. - */ - Dormant = "dormant", -} - -export enum CoinSourceType { - Withdraw = "withdraw", - Refresh = "refresh", - Tip = "tip", -} - -export interface WithdrawCoinSource { - type: CoinSourceType.Withdraw; - - /** - * Can be the empty string for orphaned coins. - */ - withdrawalGroupId: string; - - /** - * Index of the coin in the withdrawal session. - */ - coinIndex: number; - - /** - * Reserve public key for the reserve we got this coin from. - */ - reservePub: string; -} - -export interface RefreshCoinSource { - type: CoinSourceType.Refresh; - oldCoinPub: string; -} - -export interface TipCoinSource { - type: CoinSourceType.Tip; - walletTipId: string; - coinIndex: number; -} - -export type CoinSource = WithdrawCoinSource | RefreshCoinSource | TipCoinSource; - -/** - * CoinRecord as stored in the "coins" data store - * of the wallet database. - */ -export interface CoinRecord { - /** - * Where did the coin come from? Used for recouping coins. - */ - coinSource: CoinSource; - - /** - * Public key of the coin. - */ - coinPub: string; - - /** - * Private key to authorize operations on the coin. - */ - coinPriv: string; - - /** - * Key used by the exchange used to sign the coin. - */ - denomPub: string; - - /** - * Hash of the public key that signs the coin. - */ - denomPubHash: string; - - /** - * Unblinded signature by the exchange. - */ - denomSig: string; - - /** - * Amount that's left on the coin. - */ - currentAmount: AmountJson; - - /** - * Base URL that identifies the exchange from which we got the - * coin. - */ - exchangeBaseUrl: string; - - /** - * The coin is currently suspended, and will not be used for payments. - */ - suspended: boolean; - - /** - * Blinding key used when withdrawing the coin. - * Potentionally used again during payback. - */ - blindingKey: string; - - /** - * Hash of the coin envelope. - * - * Stored here for indexing purposes, so that when looking at a - * reserve history, we can quickly find the coin for a withdrawal transaction. - */ - coinEvHash: string; - - /** - * Status of the coin. - */ - status: CoinStatus; -} - -export enum ProposalStatus { - /** - * Not downloaded yet. - */ - DOWNLOADING = "downloading", - /** - * Proposal downloaded, but the user needs to accept/reject it. - */ - PROPOSED = "proposed", - /** - * The user has accepted the proposal. - */ - ACCEPTED = "accepted", - /** - * The user has rejected the proposal. - */ - REFUSED = "refused", - /** - * Downloading or processing the proposal has failed permanently. - */ - PERMANENTLY_FAILED = "permanently-failed", - /** - * Downloaded proposal was detected as a re-purchase. - */ - REPURCHASE = "repurchase", -} - -export interface ProposalDownload { - /** - * The contract that was offered by the merchant. - */ - contractTermsRaw: any; - - contractData: WalletContractData; -} - -/** - * Record for a downloaded order, stored in the wallet's database. - */ -export interface ProposalRecord { - orderId: string; - - merchantBaseUrl: string; - - /** - * Downloaded data from the merchant. - */ - download: ProposalDownload | undefined; - - /** - * Unique ID when the order is stored in the wallet DB. - */ - proposalId: string; - - /** - * Timestamp (in ms) of when the record - * was created. - */ - timestamp: Timestamp; - - /** - * Private key for the nonce. - */ - noncePriv: string; - - /** - * Public key for the nonce. - */ - noncePub: string; - - claimToken: string | undefined; - - proposalStatus: ProposalStatus; - - repurchaseProposalId: string | undefined; - - /** - * Session ID we got when downloading the contract. - */ - downloadSessionId?: string; - - /** - * Retry info, even present when the operation isn't active to allow indexing - * on the next retry timestamp. - */ - retryInfo: RetryInfo; - - lastError: TalerErrorDetails | undefined; -} - -/** - * Status of a tip we got from a merchant. - */ -export interface TipRecord { - lastError: TalerErrorDetails | undefined; - - /** - * Has the user accepted the tip? Only after the tip has been accepted coins - * withdrawn from the tip may be used. - */ - acceptedTimestamp: Timestamp | undefined; - - /** - * The tipped amount. - */ - tipAmountRaw: AmountJson; - - tipAmountEffective: AmountJson; - - /** - * Timestamp, the tip can't be picked up anymore after this deadline. - */ - tipExpiration: Timestamp; - - /** - * The exchange that will sign our coins, chosen by the merchant. - */ - exchangeBaseUrl: string; - - /** - * Base URL of the merchant that is giving us the tip. - */ - merchantBaseUrl: string; - - /** - * Denomination selection made by the wallet for picking up - * this tip. - */ - denomsSel: DenomSelectionState; - - /** - * Tip ID chosen by the wallet. - */ - walletTipId: string; - - /** - * Secret seed used to derive planchets for this tip. - */ - secretSeed: string; - - /** - * The merchant's identifier for this tip. - */ - merchantTipId: string; - - createdTimestamp: Timestamp; - - /** - * Timestamp for when the wallet finished picking up the tip - * from the merchant. - */ - pickedUpTimestamp: Timestamp | undefined; - - /** - * Retry info, even present when the operation isn't active to allow indexing - * on the next retry timestamp. - */ - retryInfo: RetryInfo; -} - -export interface RefreshGroupRecord { - /** - * Retry info, even present when the operation isn't active to allow indexing - * on the next retry timestamp. - */ - retryInfo: RetryInfo; - - lastError: TalerErrorDetails | undefined; - - lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetails }; - - refreshGroupId: string; - - reason: RefreshReason; - - oldCoinPubs: string[]; - - refreshSessionPerCoin: (RefreshSessionRecord | undefined)[]; - - inputPerCoin: AmountJson[]; - - estimatedOutputPerCoin: AmountJson[]; - - /** - * Flag for each coin whether refreshing finished. - * If a coin can't be refreshed (remaining value too small), - * it will be marked as finished, but no refresh session will - * be created. - */ - finishedPerCoin: boolean[]; - - timestampCreated: Timestamp; - - /** - * Timestamp when the refresh session finished. - */ - timestampFinished: Timestamp | undefined; -} - -/** - * Ongoing refresh - */ -export interface RefreshSessionRecord { - /** - * 512-bit secret that can be used to derive - * the other cryptographic material for the refresh session. - * - * FIXME: We currently store the derived material, but - * should always derive it. - */ - sessionSecretSeed: string; - - /** - * Sum of the value of denominations we want - * to withdraw in this session, without fees. - */ - amountRefreshOutput: AmountJson; - - /** - * Hashed denominations of the newly requested coins. - */ - newDenoms: { - denomPubHash: string; - count: number; - }[]; - - /** - * The no-reveal-index after we've done the melting. - */ - norevealIndex?: number; -} - -/** - * Wire fee for one wire method as stored in the - * wallet's database. - */ -export interface WireFee { - /** - * Fee for wire transfers. - */ - wireFee: AmountJson; - - /** - * Fees to close and refund a reserve. - */ - closingFee: AmountJson; - - /** - * Start date of the fee. - */ - startStamp: Timestamp; - - /** - * End date of the fee. - */ - endStamp: Timestamp; - - /** - * Signature made by the exchange master key. - */ - sig: string; -} - -/** - * Record to store information about a refund event. - * - * All information about a refund is stored with the purchase, - * this event is just for the history. - * - * The event is only present for completed refunds. - */ -export interface RefundEventRecord { - timestamp: Timestamp; - merchantExecutionTimestamp: Timestamp; - refundGroupId: string; - proposalId: string; -} - -export enum RefundState { - Failed = "failed", - Applied = "applied", - Pending = "pending", -} - -/** - * State of one refund from the merchant, maintained by the wallet. - */ -export type WalletRefundItem = - | WalletRefundFailedItem - | WalletRefundPendingItem - | WalletRefundAppliedItem; - -export interface WalletRefundItemCommon { - // Execution time as claimed by the merchant - executionTime: Timestamp; - - /** - * Time when the wallet became aware of the refund. - */ - obtainedTime: Timestamp; - - refundAmount: AmountJson; - - refundFee: AmountJson; - - /** - * Upper bound on the refresh cost incurred by - * applying this refund. - * - * Might be lower in practice when two refunds on the same - * coin are refreshed in the same refresh operation. - */ - totalRefreshCostBound: AmountJson; - - coinPub: string; - - rtransactionId: number; -} - -/** - * Failed refund, either because the merchant did - * something wrong or it expired. - */ -export interface WalletRefundFailedItem extends WalletRefundItemCommon { - type: RefundState.Failed; -} - -export interface WalletRefundPendingItem extends WalletRefundItemCommon { - type: RefundState.Pending; -} - -export interface WalletRefundAppliedItem extends WalletRefundItemCommon { - type: RefundState.Applied; -} - -export enum RefundReason { - /** - * Normal refund given by the merchant. - */ - NormalRefund = "normal-refund", - /** - * Refund from an aborted payment. - */ - AbortRefund = "abort-pay-refund", -} - -/** - * Record stored for every time we successfully submitted - * a payment to the merchant (both first time and re-play). - */ -export interface PayEventRecord { - proposalId: string; - sessionId: string | undefined; - isReplay: boolean; - timestamp: Timestamp; -} - -export interface ExchangeUpdatedEventRecord { - exchangeBaseUrl: string; - timestamp: Timestamp; -} - -export interface ReserveUpdatedEventRecord { - amountReserveBalance: string; - amountExpected: string; - reservePub: string; - timestamp: Timestamp; - reserveUpdateId: string; - newHistoryTransactions: ReserveTransaction[]; -} - -export interface AllowedAuditorInfo { - auditorBaseUrl: string; - auditorPub: string; -} - -export interface AllowedExchangeInfo { - exchangeBaseUrl: string; - exchangePub: string; -} - -/** - * Data extracted from the contract terms that is relevant for payment - * processing in the wallet. - */ -export interface WalletContractData { - products?: Product[]; - summaryI18n: { [lang_tag: string]: string } | undefined; - - /** - * Fulfillment URL, or the empty string if the order has no fulfillment URL. - * - * Stored as a non-nullable string as we use this field for IndexedDB indexing. - */ - fulfillmentUrl: string; - - contractTermsHash: string; - fulfillmentMessage?: string; - fulfillmentMessageI18n?: InternationalizedString; - merchantSig: string; - merchantPub: string; - merchant: MerchantInfo; - amount: AmountJson; - orderId: string; - merchantBaseUrl: string; - summary: string; - autoRefund: Duration | undefined; - maxWireFee: AmountJson; - wireFeeAmortization: number; - payDeadline: Timestamp; - refundDeadline: Timestamp; - allowedAuditors: AllowedAuditorInfo[]; - allowedExchanges: AllowedExchangeInfo[]; - timestamp: Timestamp; - wireMethod: string; - wireInfoHash: string; - maxDepositFee: AmountJson; -} - - -export enum AbortStatus { - None = "none", - AbortRefund = "abort-refund", - AbortFinished = "abort-finished", -} - -/** - * Record that stores status information about one purchase, starting from when - * the customer accepts a proposal. Includes refund status if applicable. - */ -export interface PurchaseRecord { - /** - * Proposal ID for this purchase. Uniquely identifies the - * purchase and the proposal. - */ - proposalId: string; - - /** - * Private key for the nonce. - */ - noncePriv: string; - - /** - * Public key for the nonce. - */ - noncePub: string; - - /** - * Downloaded and parsed proposal data. - */ - download: ProposalDownload; - - /** - * Deposit permissions, available once the user has accepted the payment. - * - * This value is cached and derived from payCoinSelection. - */ - coinDepositPermissions: CoinDepositPermission[] | undefined; - - payCoinSelection: PayCoinSelection; - - /** - * Pending removals from pay coin selection. - * - * Used when a the pay coin selection needs to be changed - * because a coin became known as double-spent or invalid, - * but a new coin selection can't immediately be done, as - * there is not enough balance (e.g. when waiting for a refresh). - */ - pendingRemovedCoinPubs?: string[]; - - totalPayCost: AmountJson; - - /** - * Timestamp of the first time that sending a payment to the merchant - * for this purchase was successful. - */ - timestampFirstSuccessfulPay: Timestamp | undefined; - - merchantPaySig: string | undefined; - - /** - * When was the purchase made? - * Refers to the time that the user accepted. - */ - timestampAccept: Timestamp; - - /** - * Pending refunds for the purchase. A refund is pending - * when the merchant reports a transient error from the exchange. - */ - refunds: { [refundKey: string]: WalletRefundItem }; - - /** - * When was the last refund made? - * Set to 0 if no refund was made on the purchase. - */ - timestampLastRefundStatus: Timestamp | undefined; - - /** - * Last session signature that we submitted to /pay (if any). - */ - lastSessionId: string | undefined; - - /** - * Set for the first payment, or on re-plays. - */ - paymentSubmitPending: boolean; - - /** - * Do we need to query the merchant for the refund status - * of the payment? - */ - refundQueryRequested: boolean; - - abortStatus: AbortStatus; - - payRetryInfo: RetryInfo; - - lastPayError: TalerErrorDetails | undefined; - - /** - * Retry information for querying the refund status with the merchant. - */ - refundStatusRetryInfo: RetryInfo; - - /** - * Last error (or undefined) for querying the refund status with the merchant. - */ - lastRefundStatusError: TalerErrorDetails | undefined; - - /** - * Continue querying the refund status until this deadline has expired. - */ - autoRefundDeadline: Timestamp | undefined; -} - -/** - * Configuration key/value entries to configure - * the wallet. - */ -export interface ConfigRecord<T> { - key: string; - value: T; -} - -/** - * FIXME: Eliminate this in favor of DenomSelectionState. - */ -export interface DenominationSelectionInfo { - totalCoinValue: AmountJson; - totalWithdrawCost: AmountJson; - selectedDenoms: { - /** - * How many times do we withdraw this denomination? - */ - count: number; - denom: DenominationRecord; - }[]; -} - -/** - * Selected denominations withn some extra info. - */ -export interface DenomSelectionState { - totalCoinValue: AmountJson; - totalWithdrawCost: AmountJson; - selectedDenoms: { - denomPubHash: string; - count: number; - }[]; -} - -/** - * Group of withdrawal operations that need to be executed. - * (Either for a normal withdrawal or from a tip.) - * - * The withdrawal group record is only created after we know - * the coin selection we want to withdraw. - */ -export interface WithdrawalGroupRecord { - withdrawalGroupId: string; - - /** - * Secret seed used to derive planchets. - */ - secretSeed: string; - - reservePub: string; - - exchangeBaseUrl: string; - - /** - * When was the withdrawal operation started started? - * Timestamp in milliseconds. - */ - timestampStart: Timestamp; - - /** - * When was the withdrawal operation completed? - */ - timestampFinish?: Timestamp; - - /** - * Amount including fees (i.e. the amount subtracted from the - * reserve to withdraw all coins in this withdrawal session). - */ - rawWithdrawalAmount: AmountJson; - - denomsSel: DenomSelectionState; - - /** - * Retry info, always present even on completed operations so that indexing works. - */ - retryInfo: RetryInfo; - - lastError: TalerErrorDetails | undefined; -} - -export interface BankWithdrawUriRecord { - /** - * The withdraw URI we got from the bank. - */ - talerWithdrawUri: string; - - /** - * Reserve that was created for the withdraw URI. - */ - reservePub: string; -} - -/** - * Status of recoup operations that were grouped together. - * - * The remaining amount of involved coins should be set to zero - * in the same transaction that inserts the RecoupGroupRecord. - */ -export interface RecoupGroupRecord { - /** - * Unique identifier for the recoup group record. - */ - recoupGroupId: string; - - timestampStarted: Timestamp; - - timestampFinished: Timestamp | undefined; - - /** - * Public keys that identify the coins being recouped - * as part of this session. - * - * (Structured like this to enable multiEntry indexing in IndexedDB.) - */ - coinPubs: string[]; - - /** - * Array of flags to indicate whether the recoup finished on each individual coin. - */ - recoupFinishedPerCoin: boolean[]; - - /** - * We store old amount (i.e. before recoup) of recouped coins here, - * as the balance of a recouped coin is set to zero when the - * recoup group is created. - */ - oldAmountPerCoin: AmountJson[]; - - /** - * Public keys of coins that should be scheduled for refreshing - * after all individual recoups are done. - */ - scheduleRefreshCoins: string[]; - - /** - * Retry info. - */ - retryInfo: RetryInfo; - - /** - * Last error that occured, if any. - */ - lastError: TalerErrorDetails | undefined; -} - -export enum ImportPayloadType { - CoreSchema = "core-schema", -} - -export enum BackupProviderStatus { - PaymentRequired = "payment-required", - Ready = "ready", -} - -export interface BackupProviderRecord { - baseUrl: string; - - /** - * Terms of service of the provider. - * Might be unavailable in the DB in certain situations - * (such as loading a recovery document). - */ - terms?: { - supportedProtocolVersion: string; - annualFee: AmountString; - storageLimitInMegabytes: number; - }; - - active: boolean; - - /** - * Hash of the last encrypted backup that we already merged - * or successfully uploaded ourselves. - */ - lastBackupHash?: string; - - /** - * Clock of the last backup that we already - * merged. - */ - lastBackupClock?: number; - - lastBackupTimestamp?: Timestamp; - - /** - * Proposal that we're currently trying to pay for. - * - * (Also included in paymentProposalIds.) - */ - currentPaymentProposalId?: string; - - /** - * Proposals that were used to pay (or attempt to pay) the provider. - * - * Stored to display a history of payments to the provider, and - * to make sure that the wallet isn't overpaying. - */ - paymentProposalIds: string[]; - - /** - * Next scheduled backup. - */ - nextBackupTimestamp?: Timestamp; - - /** - * Retry info. - */ - retryInfo: RetryInfo; - - /** - * Last error that occured, if any. - */ - lastError: TalerErrorDetails | undefined; -} - -/** - * Group of deposits made by the wallet. - */ -export interface DepositGroupRecord { - depositGroupId: string; - - merchantPub: string; - merchantPriv: string; - - noncePriv: string; - noncePub: string; - - /** - * Wire information used by all deposits in this - * deposit group. - */ - wire: { - payto_uri: string; - salt: string; - }; - - /** - * Verbatim contract terms. - */ - contractTermsRaw: ContractTerms; - - contractTermsHash: string; - - payCoinSelection: PayCoinSelection; - - totalPayCost: AmountJson; - - effectiveDepositAmount: AmountJson; - - depositedPerCoin: boolean[]; - - timestampCreated: Timestamp; - - timestampFinished: Timestamp | undefined; - - lastError: TalerErrorDetails | undefined; - - /** - * Retry info. - */ - retryInfo: RetryInfo; -} - -/** - * Record for a deposits that the wallet observed - * as a result of double spending, but which is not - * present in the wallet's own database otherwise. - */ -export interface GhostDepositGroupRecord { - /** - * When multiple deposits for the same contract terms hash - * have a different timestamp, we choose the earliest one. - */ - timestamp: Timestamp; - - contractTermsHash: string; - - deposits: { - coinPub: string; - amount: AmountString; - timestamp: Timestamp; - depositFee: AmountString; - merchantPub: string; - coinSig: string; - wireHash: string; - }[]; -} - -class ExchangesStore extends Store<"exchanges", ExchangeRecord> { - constructor() { - super("exchanges", { keyPath: "baseUrl" }); - } -} - -class CoinsStore extends Store<"coins", CoinRecord> { - constructor() { - super("coins", { keyPath: "coinPub" }); - } - - exchangeBaseUrlIndex = new Index< - "coins", - "exchangeBaseUrl", - string, - CoinRecord - >(this, "exchangeBaseUrl", "exchangeBaseUrl"); - - denomPubHashIndex = new Index< - "coins", - "denomPubHashIndex", - string, - CoinRecord - >(this, "denomPubHashIndex", "denomPubHash"); - - coinEvHashIndex = new Index<"coins", "coinEvHashIndex", string, CoinRecord>( - this, - "coinEvHashIndex", - "coinEvHash", - ); -} - -class ProposalsStore extends Store<"proposals", ProposalRecord> { - constructor() { - super("proposals", { keyPath: "proposalId" }); - } - urlAndOrderIdIndex = new Index< - "proposals", - "urlIndex", - string, - ProposalRecord - >(this, "urlIndex", ["merchantBaseUrl", "orderId"]); -} - -class PurchasesStore extends Store<"purchases", PurchaseRecord> { - constructor() { - super("purchases", { keyPath: "proposalId" }); - } - - fulfillmentUrlIndex = new Index< - "purchases", - "fulfillmentUrlIndex", - string, - PurchaseRecord - >(this, "fulfillmentUrlIndex", "download.contractData.fulfillmentUrl"); - - orderIdIndex = new Index<"purchases", "orderIdIndex", string, PurchaseRecord>( - this, - "orderIdIndex", - ["download.contractData.merchantBaseUrl", "download.contractData.orderId"], - ); -} - -class DenominationsStore extends Store<"denominations", DenominationRecord> { - constructor() { - // cast needed because of bug in type annotations - super("denominations", { - keyPath: (["exchangeBaseUrl", "denomPubHash"] as any) as IDBKeyPath, - }); - } - exchangeBaseUrlIndex = new Index< - "denominations", - "exchangeBaseUrlIndex", - string, - DenominationRecord - >(this, "exchangeBaseUrlIndex", "exchangeBaseUrl"); -} - -class CurrenciesStore extends Store<"currencies", CurrencyRecord> { - constructor() { - super("currencies", { keyPath: "name" }); - } -} - -class ConfigStore extends Store<"config", ConfigRecord<any>> { - constructor() { - super("config", { keyPath: "key" }); - } -} - -class ReservesStore extends Store<"reserves", ReserveRecord> { - constructor() { - super("reserves", { keyPath: "reservePub" }); - } -} - -class TipsStore extends Store<"tips", TipRecord> { - constructor() { - super("tips", { keyPath: "walletTipId" }); - } - // Added in version 2 - byMerchantTipIdAndBaseUrl = new Index< - "tips", - "tipsByMerchantTipIdAndOriginIndex", - [string, string], - TipRecord - >(this, "tipsByMerchantTipIdAndOriginIndex", [ - "merchantTipId", - "merchantBaseUrl", - ]); -} - -class WithdrawalGroupsStore extends Store< - "withdrawals", - WithdrawalGroupRecord -> { - constructor() { - super("withdrawals", { keyPath: "withdrawalGroupId" }); - } - byReservePub = new Index< - "withdrawals", - "withdrawalsByReserveIndex", - string, - WithdrawalGroupRecord - >(this, "withdrawalsByReserveIndex", "reservePub"); -} - -class PlanchetsStore extends Store<"planchets", PlanchetRecord> { - constructor() { - super("planchets", { keyPath: "coinPub" }); - } - byGroupAndIndex = new Index< - "planchets", - "withdrawalGroupAndCoinIdxIndex", - string, - PlanchetRecord - >(this, "withdrawalGroupAndCoinIdxIndex", ["withdrawalGroupId", "coinIdx"]); - byGroup = new Index< - "planchets", - "withdrawalGroupIndex", - string, - PlanchetRecord - >(this, "withdrawalGroupIndex", "withdrawalGroupId"); - - coinEvHashIndex = new Index< - "planchets", - "coinEvHashIndex", - string, - PlanchetRecord - >(this, "coinEvHashIndex", "coinEvHash"); -} - -/** - * This store is effectively a materialized index for - * reserve records that are for a bank-integrated withdrawal. - */ -class BankWithdrawUrisStore extends Store< - "bankWithdrawUris", - BankWithdrawUriRecord -> { - constructor() { - super("bankWithdrawUris", { keyPath: "talerWithdrawUri" }); - } -} - -/** - */ -class BackupProvidersStore extends Store< - "backupProviders", - BackupProviderRecord -> { - constructor() { - super("backupProviders", { keyPath: "baseUrl" }); - } -} - -class DepositGroupsStore extends Store<"depositGroups", DepositGroupRecord> { - constructor() { - super("depositGroups", { keyPath: "depositGroupId" }); - } -} - -/** - * The stores and indices for the wallet database. - */ -export const Stores = { - coins: new CoinsStore(), - config: new ConfigStore(), - currencies: new CurrenciesStore(), - denominations: new DenominationsStore(), - exchanges: new ExchangesStore(), - proposals: new ProposalsStore(), - refreshGroups: new Store<"refreshGroups", RefreshGroupRecord>( - "refreshGroups", - { - keyPath: "refreshGroupId", - }, - ), - recoupGroups: new Store<"recoupGroups", RecoupGroupRecord>("recoupGroups", { - keyPath: "recoupGroupId", - }), - reserves: new ReservesStore(), - purchases: new PurchasesStore(), - tips: new TipsStore(), - withdrawalGroups: new WithdrawalGroupsStore(), - planchets: new PlanchetsStore(), - bankWithdrawUris: new BankWithdrawUrisStore(), - backupProviders: new BackupProvidersStore(), - depositGroups: new DepositGroupsStore(), - ghostDepositGroups: new Store<"ghostDepositGroups", GhostDepositGroupRecord>( - "ghostDepositGroups", - { - keyPath: "contractTermsHash", - }, - ), -}; - -export class MetaConfigStore extends Store<"metaConfig", ConfigRecord<any>> { - constructor() { - super("metaConfig", { keyPath: "key" }); - } -} - -export const MetaStores = { - metaConfig: new MetaConfigStore(), -}; diff --git a/packages/taler-wallet-core/src/types/pendingTypes.ts b/packages/taler-wallet-core/src/types/pendingTypes.ts @@ -1,288 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type and schema definitions for pending operations in the wallet. - */ - -/** - * Imports. - */ -import { TalerErrorDetails, BalancesResponse } from "./walletTypes"; -import { ReserveRecordStatus } from "./dbTypes"; -import { Timestamp, Duration } from "../util/time"; -import { RetryInfo } from "../util/retries"; - -export enum PendingOperationType { - Bug = "bug", - ExchangeUpdate = "exchange-update", - ExchangeCheckRefresh = "exchange-check-refresh", - Pay = "pay", - ProposalChoice = "proposal-choice", - ProposalDownload = "proposal-download", - Refresh = "refresh", - Reserve = "reserve", - Recoup = "recoup", - RefundQuery = "refund-query", - TipChoice = "tip-choice", - TipPickup = "tip-pickup", - Withdraw = "withdraw", - Deposit = "deposit", -} - -/** - * Information about a pending operation. - */ -export type PendingOperationInfo = PendingOperationInfoCommon & - ( - | PendingBugOperation - | PendingExchangeUpdateOperation - | PendingExchangeCheckRefreshOperation - | PendingPayOperation - | PendingProposalChoiceOperation - | PendingProposalDownloadOperation - | PendingRefreshOperation - | PendingRefundQueryOperation - | PendingReserveOperation - | PendingTipChoiceOperation - | PendingTipPickupOperation - | PendingWithdrawOperation - | PendingRecoupOperation - | PendingDepositOperation - ); - -/** - * The wallet is currently updating information about an exchange. - */ -export interface PendingExchangeUpdateOperation { - type: PendingOperationType.ExchangeUpdate; - stage: ExchangeUpdateOperationStage; - reason: string; - exchangeBaseUrl: string; - lastError: TalerErrorDetails | undefined; -} - -/** - * The wallet should check whether coins from this exchange - * need to be auto-refreshed. - */ -export interface PendingExchangeCheckRefreshOperation { - type: PendingOperationType.ExchangeCheckRefresh; - exchangeBaseUrl: string; -} - -/** - * Some interal error happened in the wallet. This pending operation - * should *only* be reported for problems in the wallet, not when - * a problem with a merchant/exchange/etc. occurs. - */ -export interface PendingBugOperation { - type: PendingOperationType.Bug; - message: string; - details: any; -} - -/** - * Current state of an exchange update operation. - */ -export enum ExchangeUpdateOperationStage { - FetchKeys = "fetch-keys", - FetchWire = "fetch-wire", - FinalizeUpdate = "finalize-update", -} - -export enum ReserveType { - /** - * Manually created. - */ - Manual = "manual", - /** - * Withdrawn from a bank that has "tight" Taler integration - */ - TalerBankWithdraw = "taler-bank-withdraw", -} - -/** - * Status of processing a reserve. - * - * Does *not* include the withdrawal operation that might result - * from this. - */ -export interface PendingReserveOperation { - type: PendingOperationType.Reserve; - retryInfo: RetryInfo | undefined; - stage: ReserveRecordStatus; - timestampCreated: Timestamp; - reserveType: ReserveType; - reservePub: string; - bankWithdrawConfirmUrl?: string; -} - -/** - * Status of an ongoing withdrawal operation. - */ -export interface PendingRefreshOperation { - type: PendingOperationType.Refresh; - lastError?: TalerErrorDetails; - refreshGroupId: string; - finishedPerCoin: boolean[]; - retryInfo: RetryInfo; -} - -/** - * Status of downloading signed contract terms from a merchant. - */ -export interface PendingProposalDownloadOperation { - type: PendingOperationType.ProposalDownload; - merchantBaseUrl: string; - proposalTimestamp: Timestamp; - proposalId: string; - orderId: string; - lastError?: TalerErrorDetails; - retryInfo: RetryInfo; -} - -/** - * User must choose whether to accept or reject the merchant's - * proposed contract terms. - */ -export interface PendingProposalChoiceOperation { - type: PendingOperationType.ProposalChoice; - merchantBaseUrl: string; - proposalTimestamp: Timestamp; - proposalId: string; -} - -/** - * The wallet is picking up a tip that the user has accepted. - */ -export interface PendingTipPickupOperation { - type: PendingOperationType.TipPickup; - tipId: string; - merchantBaseUrl: string; - merchantTipId: string; -} - -/** - * The wallet has been offered a tip, and the user now needs to - * decide whether to accept or reject the tip. - */ -export interface PendingTipChoiceOperation { - type: PendingOperationType.TipChoice; - tipId: string; - merchantBaseUrl: string; - merchantTipId: string; -} - -/** - * The wallet is signing coins and then sending them to - * the merchant. - */ -export interface PendingPayOperation { - type: PendingOperationType.Pay; - proposalId: string; - isReplay: boolean; - retryInfo: RetryInfo; - lastError: TalerErrorDetails | undefined; -} - -/** - * The wallet is querying the merchant about whether any refund - * permissions are available for a purchase. - */ -export interface PendingRefundQueryOperation { - type: PendingOperationType.RefundQuery; - proposalId: string; - retryInfo: RetryInfo; - lastError: TalerErrorDetails | undefined; -} - -export interface PendingRecoupOperation { - type: PendingOperationType.Recoup; - recoupGroupId: string; - retryInfo: RetryInfo; - lastError: TalerErrorDetails | undefined; -} - -/** - * Status of an ongoing withdrawal operation. - */ -export interface PendingWithdrawOperation { - type: PendingOperationType.Withdraw; - lastError: TalerErrorDetails | undefined; - retryInfo: RetryInfo; - withdrawalGroupId: string; - numCoinsWithdrawn: number; - numCoinsTotal: number; -} - -/** - * Status of an ongoing deposit operation. - */ -export interface PendingDepositOperation { - type: PendingOperationType.Deposit; - lastError: TalerErrorDetails | undefined; - retryInfo: RetryInfo; - depositGroupId: string; -} - -/** - * Fields that are present in every pending operation. - */ -export interface PendingOperationInfoCommon { - /** - * Type of the pending operation. - */ - type: PendingOperationType; - - /** - * Set to true if the operation indicates that something is really in progress, - * as opposed to some regular scheduled operation or a permanent failure. - */ - givesLifeness: boolean; - - /** - * Retry info, not available on all pending operations. - * If it is available, it must have the same name. - */ - retryInfo?: RetryInfo; -} - -/** - * Response returned from the pending operations API. - */ -export interface PendingOperationsResponse { - /** - * List of pending operations. - */ - pendingOperations: PendingOperationInfo[]; - - /** - * Current wallet balance, including pending balances. - */ - walletBalance: BalancesResponse; - - /** - * When is the next pending operation due to be re-tried? - */ - nextRetryDelay: Duration; - - /** - * Does this response only include pending operations that - * are due to be executed right now? - */ - onlyDue: boolean; -} diff --git a/packages/taler-wallet-core/src/types/talerTypes.ts b/packages/taler-wallet-core/src/types/talerTypes.ts @@ -1,1457 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type and schema definitions and helpers for the core GNU Taler protocol. - * - * All types here should be "@Checkable". - * - * Even though the rest of the wallet uses camelCase for fields, use snake_case - * here, since that's the convention for the Taler JSON+HTTP API. - */ - -/** - * Imports. - */ - -import { - buildCodecForObject, - codecForString, - codecForList, - codecOptional, - codecForAny, - codecForNumber, - codecForBoolean, - codecForMap, - Codec, - codecForConstNumber, - buildCodecForUnion, - codecForConstString, -} from "../util/codec"; -import { - Timestamp, - codecForTimestamp, - Duration, - codecForDuration, -} from "../util/time"; -import { codecForAmountString } from "../util/amounts"; - -/** - * Denomination as found in the /keys response from the exchange. - */ -export class Denomination { - /** - * Value of one coin of the denomination. - */ - value: string; - - /** - * Public signing key of the denomination. - */ - denom_pub: string; - - /** - * Fee for withdrawing. - */ - fee_withdraw: string; - - /** - * Fee for depositing. - */ - fee_deposit: string; - - /** - * Fee for refreshing. - */ - fee_refresh: string; - - /** - * Fee for refunding. - */ - fee_refund: string; - - /** - * Start date from which withdraw is allowed. - */ - stamp_start: Timestamp; - - /** - * End date for withdrawing. - */ - stamp_expire_withdraw: Timestamp; - - /** - * Expiration date after which the exchange can forget about - * the currency. - */ - stamp_expire_legal: Timestamp; - - /** - * Date after which the coins of this denomination can't be - * deposited anymore. - */ - stamp_expire_deposit: Timestamp; - - /** - * Signature over the denomination information by the exchange's master - * signing key. - */ - master_sig: string; -} - -/** - * Signature by the auditor that a particular denomination key is audited. - */ -export class AuditorDenomSig { - /** - * Denomination public key's hash. - */ - denom_pub_h: string; - - /** - * The signature. - */ - auditor_sig: string; -} - -/** - * Auditor information as given by the exchange in /keys. - */ -export class Auditor { - /** - * Auditor's public key. - */ - auditor_pub: string; - - /** - * Base URL of the auditor. - */ - auditor_url: string; - - /** - * List of signatures for denominations by the auditor. - */ - denomination_keys: AuditorDenomSig[]; -} - -/** - * Request that we send to the exchange to get a payback. - */ -export interface RecoupRequest { - /** - * Hashed enomination public key of the coin we want to get - * paid back. - */ - denom_pub_hash: string; - - /** - * Signature over the coin public key by the denomination. - */ - denom_sig: string; - - /** - * Coin public key of the coin we want to refund. - */ - coin_pub: string; - - /** - * Blinding key that was used during withdraw, - * used to prove that we were actually withdrawing the coin. - */ - coin_blind_key_secret: string; - - /** - * Signature made by the coin, authorizing the payback. - */ - coin_sig: string; - - /** - * Was the coin refreshed (and thus the recoup should go to the old coin)? - */ - refreshed: boolean; -} - -/** - * Response that we get from the exchange for a payback request. - */ -export class RecoupConfirmation { - /** - * Public key of the reserve that will receive the payback. - */ - reserve_pub?: string; - - /** - * Public key of the old coin that will receive the recoup, - * provided if refreshed was true. - */ - old_coin_pub?: string; -} - -/** - * Deposit permission for a single coin. - */ -export interface CoinDepositPermission { - /** - * Signature by the coin. - */ - coin_sig: string; - /** - * Public key of the coin being spend. - */ - coin_pub: string; - /** - * Signature made by the denomination public key. - */ - ub_sig: string; - /** - * The denomination public key associated with this coin. - */ - h_denom: string; - /** - * The amount that is subtracted from this coin with this payment. - */ - contribution: string; - - /** - * URL of the exchange this coin was withdrawn from. - */ - exchange_url: string; -} - -/** - * Information about an exchange as stored inside a - * merchant's contract terms. - */ -export class ExchangeHandle { - /** - * Master public signing key of the exchange. - */ - master_pub: string; - - /** - * Base URL of the exchange. - */ - url: string; -} - -export class AuditorHandle { - /** - * Official name of the auditor. - */ - name: string; - - /** - * Master public signing key of the auditor. - */ - auditor_pub: string; - - /** - * Base URL of the auditor. - */ - url: string; -} - -// Delivery location, losely modeled as a subset of -// ISO20022's PostalAddress25. -export interface Location { - // Nation with its own government. - country?: string; - - // Identifies a subdivision of a country such as state, region, county. - country_subdivision?: string; - - // Identifies a subdivision within a country sub-division. - district?: string; - - // Name of a built-up area, with defined boundaries, and a local government. - town?: string; - - // Specific location name within the town. - town_location?: string; - - // Identifier consisting of a group of letters and/or numbers that - // is added to a postal address to assist the sorting of mail. - post_code?: string; - - // Name of a street or thoroughfare. - street?: string; - - // Name of the building or house. - building_name?: string; - - // Number that identifies the position of a building on a street. - building_number?: string; - - // Free-form address lines, should not exceed 7 elements. - address_lines?: string[]; -} - -export interface MerchantInfo { - name: string; - jurisdiction?: Location; - address?: Location; -} - -export interface Tax { - // the name of the tax - name: string; - - // amount paid in tax - tax: AmountString; -} - -export interface Product { - // merchant-internal identifier for the product. - product_id?: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n?: { [lang_tag: string]: string }; - - // The number of units of the product to deliver to the customer. - quantity?: number; - - // The unit in which the product is measured (liters, kilograms, packages, etc.) - unit?: string; - - // The price of the product; this is the total price for quantity times unit of this product. - price?: AmountString; - - // An optional base64-encoded product image - image?: string; - - // a list of taxes paid by the merchant for this product. Can be empty. - taxes?: Tax[]; - - // time indicating when this product should be delivered - delivery_date?: Timestamp; -} - -export interface InternationalizedString { - [lang_tag: string]: string; -} - -/** - * Contract terms from a merchant. - */ -export class ContractTerms { - /** - * Hash of the merchant's wire details. - */ - h_wire: string; - - /** - * Hash of the merchant's wire details. - */ - auto_refund?: Duration; - - /** - * Wire method the merchant wants to use. - */ - wire_method: string; - - /** - * Human-readable short summary of the contract. - */ - summary: string; - - summary_i18n?: InternationalizedString; - - /** - * Nonce used to ensure freshness. - */ - nonce: string; - - /** - * Total amount payable. - */ - amount: string; - - /** - * Auditors accepted by the merchant. - */ - auditors: AuditorHandle[]; - - /** - * Deadline to pay for the contract. - */ - pay_deadline: Timestamp; - - /** - * Maximum deposit fee covered by the merchant. - */ - max_fee: string; - - /** - * Information about the merchant. - */ - merchant: MerchantInfo; - - /** - * Public key of the merchant. - */ - merchant_pub: string; - - /** - * Time indicating when the order should be delivered. - * May be overwritten by individual products. - */ - delivery_date?: Timestamp; - - /** - * Delivery location for (all!) products. - */ - delivery_location?: Location; - - /** - * List of accepted exchanges. - */ - exchanges: ExchangeHandle[]; - - /** - * Products that are sold in this contract. - */ - products?: Product[]; - - /** - * Deadline for refunds. - */ - refund_deadline: Timestamp; - - /** - * Deadline for the wire transfer. - */ - wire_transfer_deadline: Timestamp; - - /** - * Time when the contract was generated by the merchant. - */ - timestamp: Timestamp; - - /** - * Order id to uniquely identify the purchase within - * one merchant instance. - */ - order_id: string; - - /** - * Base URL of the merchant's backend. - */ - merchant_base_url: string; - - /** - * Fulfillment URL to view the product or - * delivery status. - */ - fulfillment_url?: string; - - /** - * Plain text fulfillment message in the merchant's default language. - */ - fulfillment_message?: string; - - /** - * Internationalized fulfillment messages. - */ - fulfillment_message_i18n?: InternationalizedString; - - /** - * Share of the wire fee that must be settled with one payment. - */ - wire_fee_amortization?: number; - - /** - * Maximum wire fee that the merchant agrees to pay for. - */ - max_wire_fee?: string; - - /** - * Extra data, interpreted by the mechant only. - */ - extra?: any; -} - -/** - * Refund permission in the format that the merchant gives it to us. - */ -export class MerchantAbortPayRefundDetails { - /** - * Amount to be refunded. - */ - refund_amount: string; - - /** - * Fee for the refund. - */ - refund_fee: string; - - /** - * Public key of the coin being refunded. - */ - coin_pub: string; - - /** - * Refund transaction ID between merchant and exchange. - */ - rtransaction_id: number; - - /** - * Exchange's key used for the signature. - */ - exchange_pub?: string; - - /** - * Exchange's signature to confirm the refund. - */ - exchange_sig?: string; - - /** - * Error replay from the exchange (if any). - */ - exchange_reply?: any; - - /** - * Error code from the exchange (if any). - */ - exchange_code?: number; - - /** - * HTTP status code of the exchange's response - * to the merchant's refund request. - */ - exchange_http_status: number; -} - -/** - * Response for a refund pickup or a /pay in abort mode. - */ -export class MerchantRefundResponse { - /** - * Public key of the merchant - */ - merchant_pub: string; - - /** - * Contract terms hash of the contract that - * is being refunded. - */ - h_contract_terms: string; - - /** - * The signed refund permissions, to be sent to the exchange. - */ - refunds: MerchantAbortPayRefundDetails[]; -} - -/** - * Planchet detail sent to the merchant. - */ -export interface TipPlanchetDetail { - /** - * Hashed denomination public key. - */ - denom_pub_hash: string; - - /** - * Coin's blinded public key. - */ - coin_ev: string; -} - -/** - * Request sent to the merchant to pick up a tip. - */ -export interface TipPickupRequest { - /** - * Identifier of the tip. - */ - tip_id: string; - - /** - * List of planchets the wallet wants to use for the tip. - */ - planchets: TipPlanchetDetail[]; -} - -/** - * Reserve signature, defined as separate class to facilitate - * schema validation with "@Checkable". - */ -export class BlindSigWrapper { - /** - * Reserve signature. - */ - blind_sig: string; -} - -/** - * Response of the merchant - * to the TipPickupRequest. - */ -export class TipResponse { - /** - * The order of the signatures matches the planchets list. - */ - blind_sigs: BlindSigWrapper[]; -} - -/** - * Element of the payback list that the - * exchange gives us in /keys. - */ -export class Recoup { - /** - * The hash of the denomination public key for which the payback is offered. - */ - h_denom_pub: string; -} - -/** - * Structure of one exchange signing key in the /keys response. - */ -export class ExchangeSignKeyJson { - stamp_start: Timestamp; - stamp_expire: Timestamp; - stamp_end: Timestamp; - key: EddsaPublicKeyString; - master_sig: EddsaSignatureString; -} - -/** - * Structure that the exchange gives us in /keys. - */ -export class ExchangeKeysJson { - /** - * List of offered denominations. - */ - denoms: Denomination[]; - - /** - * The exchange's master public key. - */ - master_public_key: string; - - /** - * The list of auditors (partially) auditing the exchange. - */ - auditors: Auditor[]; - - /** - * Timestamp when this response was issued. - */ - list_issue_date: Timestamp; - - /** - * List of revoked denominations. - */ - recoup?: Recoup[]; - - /** - * Short-lived signing keys used to sign online - * responses. - */ - signkeys: ExchangeSignKeyJson[]; - - /** - * Protocol version. - */ - version: string; - - reserve_closing_delay: Duration; -} - -/** - * Wire fees as anounced by the exchange. - */ -export class WireFeesJson { - /** - * Cost of a wire transfer. - */ - wire_fee: string; - - /** - * Cost of clising a reserve. - */ - closing_fee: string; - - /** - * Signature made with the exchange's master key. - */ - sig: string; - - /** - * Date from which the fee applies. - */ - start_date: Timestamp; - - /** - * Data after which the fee doesn't apply anymore. - */ - end_date: Timestamp; -} - -export class AccountInfo { - payto_uri: string; - master_sig: string; -} - -export class ExchangeWireJson { - accounts: AccountInfo[]; - fees: { [methodName: string]: WireFeesJson[] }; -} - -/** - * Proposal returned from the contract URL. - */ -export class Proposal { - /** - * Contract terms for the propoal. - * Raw, un-decoded JSON object. - */ - contract_terms: any; - - /** - * Signature over contract, made by the merchant. The public key used for signing - * must be contract_terms.merchant_pub. - */ - sig: string; -} - -/** - * Response from the internal merchant API. - */ -export class CheckPaymentResponse { - order_status: string; - refunded: boolean | undefined; - refunded_amount: string | undefined; - contract_terms: any | undefined; - taler_pay_uri: string | undefined; - contract_url: string | undefined; -} - -/** - * Response from the bank. - */ -export class WithdrawOperationStatusResponse { - selection_done: boolean; - - transfer_done: boolean; - - aborted: boolean; - - amount: string; - - sender_wire?: string; - - suggested_exchange?: string; - - confirm_transfer_url?: string; - - wire_types: string[]; -} - -/** - * Response from the merchant. - */ -export class TipPickupGetResponse { - tip_amount: string; - - exchange_url: string; - - expiration: Timestamp; -} - -export class WithdrawResponse { - ev_sig: string; -} - -/** - * Easy to process format for the public data of coins - * managed by the wallet. - */ -export interface CoinDumpJson { - coins: Array<{ - /** - * The coin's denomination's public key. - */ - denom_pub: string; - /** - * Hash of denom_pub. - */ - denom_pub_hash: string; - /** - * Value of the denomination (without any fees). - */ - denom_value: string; - /** - * Public key of the coin. - */ - coin_pub: string; - /** - * Base URL of the exchange for the coin. - */ - exchange_base_url: string; - /** - * Remaining value on the coin, to the knowledge of - * the wallet. - */ - remaining_value: string; - /** - * Public key of the parent coin. - * Only present if this coin was obtained via refreshing. - */ - refresh_parent_coin_pub: string | undefined; - /** - * Public key of the reserve for this coin. - * Only present if this coin was obtained via refreshing. - */ - withdrawal_reserve_pub: string | undefined; - /** - * Is the coin suspended? - * Suspended coins are not considered for payments. - */ - coin_suspended: boolean; - }>; -} - -export interface MerchantPayResponse { - sig: string; -} - -export interface ExchangeMeltResponse { - /** - * Which of the kappa indices does the client not have to reveal. - */ - noreveal_index: number; - - /** - * Signature of TALER_RefreshMeltConfirmationPS whereby the exchange - * affirms the successful melt and confirming the noreveal_index - */ - exchange_sig: EddsaSignatureString; - - /* - * public EdDSA key of the exchange that was used to generate the signature. - * Should match one of the exchange's signing keys from /keys. Again given - * explicitly as the client might otherwise be confused by clock skew as to - * which signing key was used. - */ - exchange_pub: EddsaPublicKeyString; - - /* - * Base URL to use for operations on the refresh context - * (so the reveal operation). If not given, - * the base URL is the same as the one used for this request. - * Can be used if the base URL for /refreshes/ differs from that - * for /coins/, i.e. for load balancing. Clients SHOULD - * respect the refresh_base_url if provided. Any HTTP server - * belonging to an exchange MUST generate a 307 or 308 redirection - * to the correct base URL should a client uses the wrong base - * URL, or if the base URL has changed since the melt. - * - * When melting the same coin twice (technically allowed - * as the response might have been lost on the network), - * the exchange may return different values for the refresh_base_url. - */ - refresh_base_url?: string; -} - -export interface ExchangeRevealItem { - ev_sig: string; -} - -export interface ExchangeRevealResponse { - // List of the exchange's blinded RSA signatures on the new coins. - ev_sigs: ExchangeRevealItem[]; -} - -interface MerchantOrderStatusPaid { - /** - * Was the payment refunded (even partially, via refund or abort)? - */ - refunded: boolean; - - /** - * Amount that was refunded in total. - */ - refund_amount: AmountString; -} - -interface MerchantOrderRefundResponse { - /** - * Amount that was refunded in total. - */ - refund_amount: AmountString; - - /** - * Successful refunds for this payment, empty array for none. - */ - refunds: MerchantCoinRefundStatus[]; - - /** - * Public key of the merchant. - */ - merchant_pub: EddsaPublicKeyString; -} - -export type MerchantCoinRefundStatus = - | MerchantCoinRefundSuccessStatus - | MerchantCoinRefundFailureStatus; - -export interface MerchantCoinRefundSuccessStatus { - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // the EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund - exchange_sig: EddsaSignatureString; - - // public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; - - // Refund transaction ID. - rtransaction_id: number; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKeyString; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; - - execution_time: Timestamp; -} - -export interface MerchantCoinRefundFailureStatus { - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: number; - - // Taler error code from the exchange reply, if available. - exchange_code?: number; - - // If available, HTTP reply from the exchange. - exchange_reply?: any; - - // Refund transaction ID. - rtransaction_id: number; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKeyString; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; - - execution_time: Timestamp; -} - -export interface MerchantOrderStatusUnpaid { - /** - * URI that the wallet must process to complete the payment. - */ - taler_pay_uri: string; - - /** - * Alternative order ID which was paid for already in the same session. - * - * Only given if the same product was purchased before in the same session. - */ - already_paid_order_id?: string; -} - -/** - * Response body for the following endpoint: - * - * POST {talerBankIntegrationApi}/withdrawal-operation/{wopid} - */ -export interface BankWithdrawalOperationPostResponse { - transfer_done: boolean; -} - -export const codecForBankWithdrawalOperationPostResponse = (): Codec< - BankWithdrawalOperationPostResponse -> => - buildCodecForObject<BankWithdrawalOperationPostResponse>() - .property("transfer_done", codecForBoolean()) - .build("BankWithdrawalOperationPostResponse"); - -export type AmountString = string; -export type Base32String = string; -export type EddsaSignatureString = string; -export type EddsaPublicKeyString = string; -export type CoinPublicKeyString = string; - -export const codecForDenomination = (): Codec<Denomination> => - buildCodecForObject<Denomination>() - .property("value", codecForString()) - .property("denom_pub", codecForString()) - .property("fee_withdraw", codecForString()) - .property("fee_deposit", codecForString()) - .property("fee_refresh", codecForString()) - .property("fee_refund", codecForString()) - .property("stamp_start", codecForTimestamp) - .property("stamp_expire_withdraw", codecForTimestamp) - .property("stamp_expire_legal", codecForTimestamp) - .property("stamp_expire_deposit", codecForTimestamp) - .property("master_sig", codecForString()) - .build("Denomination"); - -export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => - buildCodecForObject<AuditorDenomSig>() - .property("denom_pub_h", codecForString()) - .property("auditor_sig", codecForString()) - .build("AuditorDenomSig"); - -export const codecForAuditor = (): Codec<Auditor> => - buildCodecForObject<Auditor>() - .property("auditor_pub", codecForString()) - .property("auditor_url", codecForString()) - .property("denomination_keys", codecForList(codecForAuditorDenomSig())) - .build("Auditor"); - -export const codecForExchangeHandle = (): Codec<ExchangeHandle> => - buildCodecForObject<ExchangeHandle>() - .property("master_pub", codecForString()) - .property("url", codecForString()) - .build("ExchangeHandle"); - -export const codecForAuditorHandle = (): Codec<AuditorHandle> => - buildCodecForObject<AuditorHandle>() - .property("name", codecForString()) - .property("auditor_pub", codecForString()) - .property("url", codecForString()) - .build("AuditorHandle"); - -export const codecForLocation = (): Codec<Location> => - buildCodecForObject<Location>() - .property("country", codecOptional(codecForString())) - .property("country_subdivision", codecOptional(codecForString())) - .property("building_name", codecOptional(codecForString())) - .property("building_number", codecOptional(codecForString())) - .property("district", codecOptional(codecForString())) - .property("street", codecOptional(codecForString())) - .property("post_code", codecOptional(codecForString())) - .property("town", codecOptional(codecForString())) - .property("town_location", codecOptional(codecForString())) - .property("address_lines", codecOptional(codecForList(codecForString()))) - .build("Location"); - -export const codecForMerchantInfo = (): Codec<MerchantInfo> => - buildCodecForObject<MerchantInfo>() - .property("name", codecForString()) - .property("address", codecOptional(codecForLocation())) - .property("jurisdiction", codecOptional(codecForLocation())) - .build("MerchantInfo"); - -export const codecForTax = (): Codec<Tax> => - buildCodecForObject<Tax>() - .property("name", codecForString()) - .property("tax", codecForString()) - .build("Tax"); - -export const codecForInternationalizedString = (): Codec< - InternationalizedString -> => codecForMap(codecForString()); - -export const codecForProduct = (): Codec<Product> => - buildCodecForObject<Product>() - .property("product_id", codecOptional(codecForString())) - .property("description", codecForString()) - .property( - "description_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("quantity", codecOptional(codecForNumber())) - .property("unit", codecOptional(codecForString())) - .property("price", codecOptional(codecForString())) - .build("Tax"); - -export const codecForContractTerms = (): Codec<ContractTerms> => - buildCodecForObject<ContractTerms>() - .property("order_id", codecForString()) - .property("fulfillment_url", codecOptional(codecForString())) - .property("fulfillment_message", codecOptional(codecForString())) - .property( - "fulfillment_message_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("merchant_base_url", codecForString()) - .property("h_wire", codecForString()) - .property("auto_refund", codecOptional(codecForDuration)) - .property("wire_method", codecForString()) - .property("summary", codecForString()) - .property("summary_i18n", codecOptional(codecForInternationalizedString())) - .property("nonce", codecForString()) - .property("amount", codecForString()) - .property("auditors", codecForList(codecForAuditorHandle())) - .property("pay_deadline", codecForTimestamp) - .property("refund_deadline", codecForTimestamp) - .property("wire_transfer_deadline", codecForTimestamp) - .property("timestamp", codecForTimestamp) - .property("delivery_location", codecOptional(codecForLocation())) - .property("delivery_date", codecOptional(codecForTimestamp)) - .property("max_fee", codecForString()) - .property("max_wire_fee", codecOptional(codecForString())) - .property("merchant", codecForMerchantInfo()) - .property("merchant_pub", codecForString()) - .property("exchanges", codecForList(codecForExchangeHandle())) - .property("products", codecOptional(codecForList(codecForProduct()))) - .property("extra", codecForAny()) - .build("ContractTerms"); - -export const codecForMerchantRefundPermission = (): Codec< - MerchantAbortPayRefundDetails -> => - buildCodecForObject<MerchantAbortPayRefundDetails>() - .property("refund_amount", codecForAmountString()) - .property("refund_fee", codecForAmountString()) - .property("coin_pub", codecForString()) - .property("rtransaction_id", codecForNumber()) - .property("exchange_http_status", codecForNumber()) - .property("exchange_code", codecOptional(codecForNumber())) - .property("exchange_reply", codecOptional(codecForAny())) - .property("exchange_sig", codecOptional(codecForString())) - .property("exchange_pub", codecOptional(codecForString())) - .build("MerchantRefundPermission"); - -export const codecForMerchantRefundResponse = (): Codec< - MerchantRefundResponse -> => - buildCodecForObject<MerchantRefundResponse>() - .property("merchant_pub", codecForString()) - .property("h_contract_terms", codecForString()) - .property("refunds", codecForList(codecForMerchantRefundPermission())) - .build("MerchantRefundResponse"); - -export const codecForBlindSigWrapper = (): Codec<BlindSigWrapper> => - buildCodecForObject<BlindSigWrapper>() - .property("blind_sig", codecForString()) - .build("BlindSigWrapper"); - -export const codecForTipResponse = (): Codec<TipResponse> => - buildCodecForObject<TipResponse>() - .property("blind_sigs", codecForList(codecForBlindSigWrapper())) - .build("TipResponse"); - -export const codecForRecoup = (): Codec<Recoup> => - buildCodecForObject<Recoup>() - .property("h_denom_pub", codecForString()) - .build("Recoup"); - -export const codecForExchangeSigningKey = (): Codec<ExchangeSignKeyJson> => - buildCodecForObject<ExchangeSignKeyJson>() - .property("key", codecForString()) - .property("master_sig", codecForString()) - .property("stamp_end", codecForTimestamp) - .property("stamp_start", codecForTimestamp) - .property("stamp_expire", codecForTimestamp) - .build("ExchangeSignKeyJson"); - -export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => - buildCodecForObject<ExchangeKeysJson>() - .property("denoms", codecForList(codecForDenomination())) - .property("master_public_key", codecForString()) - .property("auditors", codecForList(codecForAuditor())) - .property("list_issue_date", codecForTimestamp) - .property("recoup", codecOptional(codecForList(codecForRecoup()))) - .property("signkeys", codecForList(codecForExchangeSigningKey())) - .property("version", codecForString()) - .property("reserve_closing_delay", codecForDuration) - .build("KeysJson"); - -export const codecForWireFeesJson = (): Codec<WireFeesJson> => - buildCodecForObject<WireFeesJson>() - .property("wire_fee", codecForString()) - .property("closing_fee", codecForString()) - .property("sig", codecForString()) - .property("start_date", codecForTimestamp) - .property("end_date", codecForTimestamp) - .build("WireFeesJson"); - -export const codecForAccountInfo = (): Codec<AccountInfo> => - buildCodecForObject<AccountInfo>() - .property("payto_uri", codecForString()) - .property("master_sig", codecForString()) - .build("AccountInfo"); - -export const codecForExchangeWireJson = (): Codec<ExchangeWireJson> => - buildCodecForObject<ExchangeWireJson>() - .property("accounts", codecForList(codecForAccountInfo())) - .property("fees", codecForMap(codecForList(codecForWireFeesJson()))) - .build("ExchangeWireJson"); - -export const codecForProposal = (): Codec<Proposal> => - buildCodecForObject<Proposal>() - .property("contract_terms", codecForAny()) - .property("sig", codecForString()) - .build("Proposal"); - -export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> => - buildCodecForObject<CheckPaymentResponse>() - .property("order_status", codecForString()) - .property("refunded", codecOptional(codecForBoolean())) - .property("refunded_amount", codecOptional(codecForString())) - .property("contract_terms", codecOptional(codecForAny())) - .property("taler_pay_uri", codecOptional(codecForString())) - .property("contract_url", codecOptional(codecForString())) - .build("CheckPaymentResponse"); - -export const codecForWithdrawOperationStatusResponse = (): Codec< - WithdrawOperationStatusResponse -> => - buildCodecForObject<WithdrawOperationStatusResponse>() - .property("selection_done", codecForBoolean()) - .property("transfer_done", codecForBoolean()) - .property("aborted", codecForBoolean()) - .property("amount", codecForString()) - .property("sender_wire", codecOptional(codecForString())) - .property("suggested_exchange", codecOptional(codecForString())) - .property("confirm_transfer_url", codecOptional(codecForString())) - .property("wire_types", codecForList(codecForString())) - .build("WithdrawOperationStatusResponse"); - -export const codecForTipPickupGetResponse = (): Codec<TipPickupGetResponse> => - buildCodecForObject<TipPickupGetResponse>() - .property("tip_amount", codecForString()) - .property("exchange_url", codecForString()) - .property("expiration", codecForTimestamp) - .build("TipPickupGetResponse"); - -export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> => - buildCodecForObject<RecoupConfirmation>() - .property("reserve_pub", codecOptional(codecForString())) - .property("old_coin_pub", codecOptional(codecForString())) - .build("RecoupConfirmation"); - -export const codecForWithdrawResponse = (): Codec<WithdrawResponse> => - buildCodecForObject<WithdrawResponse>() - .property("ev_sig", codecForString()) - .build("WithdrawResponse"); - -export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => - buildCodecForObject<MerchantPayResponse>() - .property("sig", codecForString()) - .build("MerchantPayResponse"); - -export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> => - buildCodecForObject<ExchangeMeltResponse>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("noreveal_index", codecForNumber()) - .property("refresh_base_url", codecOptional(codecForString())) - .build("ExchangeMeltResponse"); - -export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> => - buildCodecForObject<ExchangeRevealItem>() - .property("ev_sig", codecForString()) - .build("ExchangeRevealItem"); - -export const codecForExchangeRevealResponse = (): Codec< - ExchangeRevealResponse -> => - buildCodecForObject<ExchangeRevealResponse>() - .property("ev_sigs", codecForList(codecForExchangeRevealItem())) - .build("ExchangeRevealResponse"); - -export const codecForMerchantCoinRefundSuccessStatus = (): Codec< - MerchantCoinRefundSuccessStatus -> => - buildCodecForObject<MerchantCoinRefundSuccessStatus>() - .property("type", codecForConstString("success")) - .property("coin_pub", codecForString()) - .property("exchange_status", codecForConstNumber(200)) - .property("exchange_sig", codecForString()) - .property("rtransaction_id", codecForNumber()) - .property("refund_amount", codecForString()) - .property("exchange_pub", codecForString()) - .property("execution_time", codecForTimestamp) - .build("MerchantCoinRefundSuccessStatus"); - -export const codecForMerchantCoinRefundFailureStatus = (): Codec< - MerchantCoinRefundFailureStatus -> => - buildCodecForObject<MerchantCoinRefundFailureStatus>() - .property("type", codecForConstString("failure")) - .property("coin_pub", codecForString()) - .property("exchange_status", codecForNumber()) - .property("rtransaction_id", codecForNumber()) - .property("refund_amount", codecForString()) - .property("exchange_code", codecOptional(codecForNumber())) - .property("exchange_reply", codecOptional(codecForAny())) - .property("execution_time", codecForTimestamp) - .build("MerchantCoinRefundFailureStatus"); - -export const codecForMerchantCoinRefundStatus = (): Codec< - MerchantCoinRefundStatus -> => - buildCodecForUnion<MerchantCoinRefundStatus>() - .discriminateOn("type") - .alternative("success", codecForMerchantCoinRefundSuccessStatus()) - .alternative("failure", codecForMerchantCoinRefundFailureStatus()) - .build("MerchantCoinRefundStatus"); - -export const codecForMerchantOrderStatusPaid = (): Codec< - MerchantOrderStatusPaid -> => - buildCodecForObject<MerchantOrderStatusPaid>() - .property("refund_amount", codecForString()) - .property("refunded", codecForBoolean()) - .build("MerchantOrderStatusPaid"); - -export const codecForMerchantOrderRefundPickupResponse = (): Codec< - MerchantOrderRefundResponse -> => - buildCodecForObject<MerchantOrderRefundResponse>() - .property("merchant_pub", codecForString()) - .property("refund_amount", codecForString()) - .property("refunds", codecForList(codecForMerchantCoinRefundStatus())) - .build("MerchantOrderRefundPickupResponse"); - -export const codecForMerchantOrderStatusUnpaid = (): Codec< - MerchantOrderStatusUnpaid -> => - buildCodecForObject<MerchantOrderStatusUnpaid>() - .property("taler_pay_uri", codecForString()) - .property("already_paid_order_id", codecOptional(codecForString())) - .build("MerchantOrderStatusUnpaid"); - -export interface AbortRequest { - // hash of the order's contract terms (this is used to authenticate the - // wallet/customer in case $ORDER_ID is guessable). - h_contract: string; - - // List of coins the wallet would like to see refunds for. - // (Should be limited to the coins for which the original - // payment succeeded, as far as the wallet knows.) - coins: AbortingCoin[]; -} - -export interface AbortingCoin { - // Public key of a coin for which the wallet is requesting an abort-related refund. - coin_pub: EddsaPublicKeyString; - - // The amount to be refunded (matches the original contribution) - contribution: AmountString; - - // URL of the exchange this coin was withdrawn from. - exchange_url: string; -} - -export interface AbortResponse { - // List of refund responses about the coins that the wallet - // requested an abort for. In the same order as the 'coins' - // from the original request. - // The rtransaction_id is implied to be 0. - refunds: MerchantAbortPayRefundStatus[]; -} - -export const codecForAbortResponse = (): Codec<AbortResponse> => - buildCodecForObject<AbortResponse>() - .property("refunds", codecForList(codecForMerchantAbortPayRefundStatus())) - .build("AbortResponse"); - -export type MerchantAbortPayRefundStatus = - | MerchantAbortPayRefundSuccessStatus - | MerchantAbortPayRefundFailureStatus; - -// Details about why a refund failed. -export interface MerchantAbortPayRefundFailureStatus { - // Used as tag for the sum type RefundStatus sum type. - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: number; - - // Taler error code from the exchange reply, if available. - exchange_code?: number; - - // If available, HTTP reply from the exchange. - exchange_reply?: unknown; -} - -// Additional details needed to verify the refund confirmation signature -// (h_contract_terms and merchant_pub) are already known -// to the wallet and thus not included. -export interface MerchantAbortPayRefundSuccessStatus { - // Used as tag for the sum type MerchantCoinRefundStatus sum type. - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // the EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund - exchange_sig: string; - - // public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: string; -} - -export const codecForMerchantAbortPayRefundSuccessStatus = (): Codec< - MerchantAbortPayRefundSuccessStatus -> => - buildCodecForObject<MerchantAbortPayRefundSuccessStatus>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("exchange_status", codecForConstNumber(200)) - .property("type", codecForConstString("success")) - .build("MerchantAbortPayRefundSuccessStatus"); - -export const codecForMerchantAbortPayRefundFailureStatus = (): Codec< - MerchantAbortPayRefundFailureStatus -> => - buildCodecForObject<MerchantAbortPayRefundFailureStatus>() - .property("exchange_code", codecForNumber()) - .property("exchange_reply", codecForAny()) - .property("exchange_status", codecForNumber()) - .property("type", codecForConstString("failure")) - .build("MerchantAbortPayRefundFailureStatus"); - -export const codecForMerchantAbortPayRefundStatus = (): Codec< - MerchantAbortPayRefundStatus -> => - buildCodecForUnion<MerchantAbortPayRefundStatus>() - .discriminateOn("type") - .alternative("success", codecForMerchantAbortPayRefundSuccessStatus()) - .alternative("failure", codecForMerchantAbortPayRefundFailureStatus()) - .build("MerchantAbortPayRefundStatus"); - -export interface TalerConfigResponse { - name: string; - version: string; - currency?: string; -} - -export const codecForTalerConfigResponse = (): Codec<TalerConfigResponse> => - buildCodecForObject<TalerConfigResponse>() - .property("name", codecForString()) - .property("version", codecForString()) - .property("currency", codecOptional(codecForString())) - .build("TalerConfigResponse"); diff --git a/packages/taler-wallet-core/src/types/transactionsTypes.ts b/packages/taler-wallet-core/src/types/transactionsTypes.ts @@ -1,364 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type and schema definitions for the wallet's transaction list. - * - * @author Florian Dold - * @author Torsten Grote - */ - -/** - * Imports. - */ -import { Timestamp } from "../util/time"; -import { - AmountString, - Product, - InternationalizedString, - MerchantInfo, - codecForInternationalizedString, - codecForMerchantInfo, - codecForProduct, -} from "./talerTypes"; -import { - Codec, - buildCodecForObject, - codecOptional, - codecForString, - codecForList, - codecForAny, -} from "../util/codec"; -import { TalerErrorDetails } from "./walletTypes"; - -export interface TransactionsRequest { - /** - * return only transactions in the given currency - */ - currency?: string; - - /** - * if present, results will be limited to transactions related to the given search string - */ - search?: string; -} - -export interface TransactionsResponse { - // a list of past and pending transactions sorted by pending, timestamp and transactionId. - // In case two events are both pending and have the same timestamp, - // they are sorted by the transactionId - // (lexically ascending and locale-independent comparison). - transactions: Transaction[]; -} - -export interface TransactionCommon { - // opaque unique ID for the transaction, used as a starting point for paginating queries - // and for invoking actions on the transaction (e.g. deleting/hiding it from the history) - transactionId: string; - - // the type of the transaction; different types might provide additional information - type: TransactionType; - - // main timestamp of the transaction - timestamp: Timestamp; - - // true if the transaction is still pending, false otherwise - // If a transaction is not longer pending, its timestamp will be updated, - // but its transactionId will remain unchanged - pending: boolean; - - // Raw amount of the transaction (exclusive of fees or other extra costs) - amountRaw: AmountString; - - // Amount added or removed from the wallet's balance (including all fees and other costs) - amountEffective: AmountString; - - error?: TalerErrorDetails; -} - -export type Transaction = - | TransactionWithdrawal - | TransactionPayment - | TransactionRefund - | TransactionTip - | TransactionRefresh - | TransactionDeposit; - -export enum TransactionType { - Withdrawal = "withdrawal", - Payment = "payment", - Refund = "refund", - Refresh = "refresh", - Tip = "tip", - Deposit = "deposit", -} - -export enum WithdrawalType { - TalerBankIntegrationApi = "taler-bank-integration-api", - ManualTransfer = "manual-transfer", -} - -export type WithdrawalDetails = - | WithdrawalDetailsForManualTransfer - | WithdrawalDetailsForTalerBankIntegrationApi; - -interface WithdrawalDetailsForManualTransfer { - type: WithdrawalType.ManualTransfer; - - /** - * Payto URIs that the exchange supports. - * - * Already contains the amount and message. - */ - exchangePaytoUris: string[]; -} - -interface WithdrawalDetailsForTalerBankIntegrationApi { - type: WithdrawalType.TalerBankIntegrationApi; - - /** - * Set to true if the bank has confirmed the withdrawal, false if not. - * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI. - * See also bankConfirmationUrl below. - */ - confirmed: boolean; - - /** - * If the withdrawal is unconfirmed, this can include a URL for user - * initiated confirmation. - */ - bankConfirmationUrl?: string; -} - -// This should only be used for actual withdrawals -// and not for tips that have their own transactions type. -interface TransactionWithdrawal extends TransactionCommon { - type: TransactionType.Withdrawal; - - /** - * Exchange of the withdrawal. - */ - exchangeBaseUrl: string; - - /** - * Amount that got subtracted from the reserve balance. - */ - amountRaw: AmountString; - - /** - * Amount that actually was (or will be) added to the wallet's balance. - */ - amountEffective: AmountString; - - withdrawalDetails: WithdrawalDetails; -} - -export enum PaymentStatus { - /** - * Explicitly aborted after timeout / failure - */ - Aborted = "aborted", - - /** - * Payment failed, wallet will auto-retry. - * User should be given the option to retry now / abort. - */ - Failed = "failed", - - /** - * Paid successfully - */ - Paid = "paid", - - /** - * User accepted, payment is processing. - */ - Accepted = "accepted", -} - -export interface TransactionPayment extends TransactionCommon { - type: TransactionType.Payment; - - /** - * Additional information about the payment. - */ - info: OrderShortInfo; - - /** - * Wallet-internal end-to-end identifier for the payment. - */ - proposalId: string; - - /** - * How far did the wallet get with processing the payment? - */ - status: PaymentStatus; - - /** - * Amount that must be paid for the contract - */ - amountRaw: AmountString; - - /** - * Amount that was paid, including deposit, wire and refresh fees. - */ - amountEffective: AmountString; -} - -export interface OrderShortInfo { - /** - * Order ID, uniquely identifies the order within a merchant instance - */ - orderId: string; - - /** - * Hash of the contract terms. - */ - contractTermsHash: string; - - /** - * More information about the merchant - */ - merchant: MerchantInfo; - - /** - * Summary of the order, given by the merchant - */ - summary: string; - - /** - * Map from IETF BCP 47 language tags to localized summaries - */ - summary_i18n?: InternationalizedString; - - /** - * List of products that are part of the order - */ - products: Product[] | undefined; - - /** - * URL of the fulfillment, given by the merchant - */ - fulfillmentUrl?: string; - - /** - * Plain text message that should be shown to the user - * when the payment is complete. - */ - fulfillmentMessage?: string; - - /** - * Translations of fulfillmentMessage. - */ - fulfillmentMessage_i18n?: InternationalizedString; -} - -interface TransactionRefund extends TransactionCommon { - type: TransactionType.Refund; - - // ID for the transaction that is refunded - refundedTransactionId: string; - - // Additional information about the refunded payment - info: OrderShortInfo; - - // Amount that has been refunded by the merchant - amountRaw: AmountString; - - // Amount will be added to the wallet's balance after fees and refreshing - amountEffective: AmountString; -} - -interface TransactionTip extends TransactionCommon { - type: TransactionType.Tip; - - // Raw amount of the tip, without extra fees that apply - amountRaw: AmountString; - - // Amount will be (or was) added to the wallet's balance after fees and refreshing - amountEffective: AmountString; - - merchantBaseUrl: string; -} - -// A transaction shown for refreshes that are not associated to other transactions -// such as a refresh necessary before coin expiration. -// It should only be returned by the API if the effective amount is different from zero. -interface TransactionRefresh extends TransactionCommon { - type: TransactionType.Refresh; - - // Exchange that the coins are refreshed with - exchangeBaseUrl: string; - - // Raw amount that is refreshed - amountRaw: AmountString; - - // Amount that will be paid as fees for the refresh - amountEffective: AmountString; -} - -/** - * Deposit transaction, which effectively sends - * money from this wallet somewhere else. - */ -interface TransactionDeposit extends TransactionCommon { - type: TransactionType.Deposit; - - depositGroupId: string; - - /** - * Target for the deposit. - */ - targetPaytoUri: string; - - /** - * Raw amount that is being deposited - */ - amountRaw: AmountString; - - /** - * Effective amount that is being deposited - */ - amountEffective: AmountString; -} - -export const codecForTransactionsRequest = (): Codec<TransactionsRequest> => - buildCodecForObject<TransactionsRequest>() - .property("currency", codecOptional(codecForString())) - .property("search", codecOptional(codecForString())) - .build("TransactionsRequest"); - -// FIXME: do full validation here! -export const codecForTransactionsResponse = (): Codec<TransactionsResponse> => - buildCodecForObject<TransactionsResponse>() - .property("transactions", codecForList(codecForAny())) - .build("TransactionsResponse"); - -export const codecForOrderShortInfo = (): Codec<OrderShortInfo> => - buildCodecForObject<OrderShortInfo>() - .property("contractTermsHash", codecForString()) - .property("fulfillmentMessage", codecOptional(codecForString())) - .property( - "fulfillmentMessage_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("fulfillmentUrl", codecOptional(codecForString())) - .property("merchant", codecForMerchantInfo()) - .property("orderId", codecForString()) - .property("products", codecOptional(codecForList(codecForProduct()))) - .property("summary", codecForString()) - .property("summary_i18n", codecOptional(codecForInternationalizedString())) - .build("OrderShortInfo"); diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts @@ -1,1026 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2015-2020 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Types used by clients of the wallet. - * - * These types are defined in a separate file make tree shaking easier, since - * some components use these types (via RPC) but do not depend on the wallet - * code directly. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { - AmountJson, - codecForAmountJson, - codecForAmountString, -} from "../util/amounts"; -import * as LibtoolVersion from "../util/libtoolVersion"; -import { - ExchangeRecord, - ExchangeWireInfo, - DenominationSelectionInfo, -} from "./dbTypes"; -import { Timestamp, codecForTimestamp } from "../util/time"; -import { - buildCodecForObject, - codecForString, - codecOptional, - Codec, - codecForList, - codecForBoolean, - codecForConstString, - codecForAny, - buildCodecForUnion, -} from "../util/codec"; -import { - AmountString, - codecForContractTerms, - ContractTerms, -} from "./talerTypes"; -import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes"; -import { BackupRecovery } from "../operations/backup"; - -/** - * Response for the create reserve request to the wallet. - */ -export class CreateReserveResponse { - /** - * Exchange URL where the bank should create the reserve. - * The URL is canonicalized in the response. - */ - exchange: string; - - /** - * Reserve public key of the newly created reserve. - */ - reservePub: string; -} - -/** - * Information about what will happen when creating a reserve. - * - * Sent to the wallet frontend to be rendered and shown to the user. - */ -export interface ExchangeWithdrawDetails { - /** - * Exchange that the reserve will be created at. - */ - exchangeInfo: ExchangeRecord; - - /** - * Filtered wire info to send to the bank. - */ - exchangeWireAccounts: string[]; - - /** - * Selected denominations for withdraw. - */ - selectedDenoms: DenominationSelectionInfo; - - /** - * Fees for withdraw. - */ - withdrawFee: AmountJson; - - /** - * Remaining balance that is too small to be withdrawn. - */ - overhead: AmountJson; - - /** - * Wire fees from the exchange. - */ - wireFees: ExchangeWireInfo; - - /** - * Does the wallet know about an auditor for - * the exchange that the reserve. - */ - isAudited: boolean; - - /** - * Did the user already accept the current terms of service for the exchange? - */ - termsOfServiceAccepted: boolean; - - /** - * The exchange is trusted directly. - */ - isTrusted: boolean; - - /** - * The earliest deposit expiration of the selected coins. - */ - earliestDepositExpiration: Timestamp; - - /** - * Number of currently offered denominations. - */ - numOfferedDenoms: number; - - /** - * Public keys of trusted auditors for the currency we're withdrawing. - */ - trustedAuditorPubs: string[]; - - /** - * Result of checking the wallet's version - * against the exchange's version. - * - * Older exchanges don't return version information. - */ - versionMatch: LibtoolVersion.VersionMatchResult | undefined; - - /** - * Libtool-style version string for the exchange or "unknown" - * for older exchanges. - */ - exchangeVersion: string; - - /** - * Libtool-style version string for the wallet. - */ - walletVersion: string; -} - -export interface Balance { - available: AmountString; - pendingIncoming: AmountString; - pendingOutgoing: AmountString; - - // Does the balance for this currency have a pending - // transaction? - hasPendingTransactions: boolean; - - // Is there a pending transaction that would affect the balance - // and requires user input? - requiresUserInput: boolean; -} - -export interface BalancesResponse { - balances: Balance[]; -} - -export const codecForBalance = (): Codec<Balance> => - buildCodecForObject<Balance>() - .property("available", codecForString()) - .property("hasPendingTransactions", codecForBoolean()) - .property("pendingIncoming", codecForString()) - .property("pendingOutgoing", codecForString()) - .property("requiresUserInput", codecForBoolean()) - .build("Balance"); - -export const codecForBalancesResponse = (): Codec<BalancesResponse> => - buildCodecForObject<BalancesResponse>() - .property("balances", codecForList(codecForBalance())) - .build("BalancesResponse"); - -/** - * For terseness. - */ -export function mkAmount( - value: number, - fraction: number, - currency: string, -): AmountJson { - return { value, fraction, currency }; -} - -export enum ConfirmPayResultType { - Done = "done", - Pending = "pending", -} - -/** - * Result for confirmPay - */ -export interface ConfirmPayResultDone { - type: ConfirmPayResultType.Done; - contractTerms: ContractTerms; -} - -export interface ConfirmPayResultPending { - type: ConfirmPayResultType.Pending; - - lastError: TalerErrorDetails; -} - -export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending; - -export const codecForConfirmPayResultPending = (): Codec<ConfirmPayResultPending> => - buildCodecForObject<ConfirmPayResultPending>() - .property("lastError", codecForAny()) - .property("type", codecForConstString(ConfirmPayResultType.Pending)) - .build("ConfirmPayResultPending"); - -export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> => - buildCodecForObject<ConfirmPayResultDone>() - .property("type", codecForConstString(ConfirmPayResultType.Done)) - .property("contractTerms", codecForContractTerms()) - .build("ConfirmPayResultDone"); - -export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> => - buildCodecForUnion<ConfirmPayResult>() - .discriminateOn("type") - .alternative( - ConfirmPayResultType.Pending, - codecForConfirmPayResultPending(), - ) - .alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone()) - .build("ConfirmPayResult"); - -/** - * Information about all sender wire details known to the wallet, - * as well as exchanges that accept these wire types. - */ -export interface SenderWireInfos { - /** - * Mapping from exchange base url to list of accepted - * wire types. - */ - exchangeWireTypes: { [exchangeBaseUrl: string]: string[] }; - - /** - * Sender wire information stored in the wallet. - */ - senderWires: string[]; -} - -/** - * Request to create a reserve. - */ -export interface CreateReserveRequest { - /** - * The initial amount for the reserve. - */ - amount: AmountJson; - - /** - * Exchange URL where the bank should create the reserve. - */ - exchange: string; - - /** - * Payto URI that identifies the exchange's account that the funds - * for this reserve go into. - */ - exchangePaytoUri?: string; - - /** - * Wire details (as a payto URI) for the bank account that sent the funds to - * the exchange. - */ - senderWire?: string; - - /** - * URL to fetch the withdraw status from the bank. - */ - bankWithdrawStatusUrl?: string; -} - -export const codecForCreateReserveRequest = (): Codec<CreateReserveRequest> => - buildCodecForObject<CreateReserveRequest>() - .property("amount", codecForAmountJson()) - .property("exchange", codecForString()) - .property("exchangePaytoUri", codecForString()) - .property("senderWire", codecOptional(codecForString())) - .property("bankWithdrawStatusUrl", codecOptional(codecForString())) - .build("CreateReserveRequest"); - -/** - * Request to mark a reserve as confirmed. - */ -export interface ConfirmReserveRequest { - /** - * Public key of then reserve that should be marked - * as confirmed. - */ - reservePub: string; -} - -export const codecForConfirmReserveRequest = (): Codec<ConfirmReserveRequest> => - buildCodecForObject<ConfirmReserveRequest>() - .property("reservePub", codecForString()) - .build("ConfirmReserveRequest"); - -/** - * Wire coins to the user's own bank account. - */ -export class ReturnCoinsRequest { - /** - * The amount to wire. - */ - amount: AmountJson; - - /** - * The exchange to take the coins from. - */ - exchange: string; - - /** - * Wire details for the bank account of the customer that will - * receive the funds. - */ - senderWire?: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => ReturnCoinsRequest; -} - -export interface PrepareTipResult { - /** - * Unique ID for the tip assigned by the wallet. - * Typically different from the merchant-generated tip ID. - */ - walletTipId: string; - - /** - * Has the tip already been accepted? - */ - accepted: boolean; - - /** - * Amount that the merchant gave. - */ - tipAmountRaw: AmountString; - - /** - * Amount that arrived at the wallet. - * Might be lower than the raw amount due to fees. - */ - tipAmountEffective: AmountString; - - /** - * Base URL of the merchant backend giving then tip. - */ - merchantBaseUrl: string; - - /** - * Base URL of the exchange that is used to withdraw the tip. - * Determined by the merchant, the wallet/user has no choice here. - */ - exchangeBaseUrl: string; - - /** - * Time when the tip will expire. After it expired, it can't be picked - * up anymore. - */ - expirationTimestamp: Timestamp; -} - -export const codecForPrepareTipResult = (): Codec<PrepareTipResult> => - buildCodecForObject<PrepareTipResult>() - .property("accepted", codecForBoolean()) - .property("tipAmountRaw", codecForAmountString()) - .property("tipAmountEffective", codecForAmountString()) - .property("exchangeBaseUrl", codecForString()) - .property("merchantBaseUrl", codecForString()) - .property("expirationTimestamp", codecForTimestamp) - .property("walletTipId", codecForString()) - .build("PrepareTipResult"); - -export interface BenchmarkResult { - time: { [s: string]: number }; - repetitions: number; -} - -export enum PreparePayResultType { - PaymentPossible = "payment-possible", - InsufficientBalance = "insufficient-balance", - AlreadyConfirmed = "already-confirmed", -} - -export const codecForPreparePayResultPaymentPossible = (): Codec<PreparePayResultPaymentPossible> => - buildCodecForObject<PreparePayResultPaymentPossible>() - .property("amountEffective", codecForAmountString()) - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForContractTerms()) - .property("proposalId", codecForString()) - .property( - "status", - codecForConstString(PreparePayResultType.PaymentPossible), - ) - .build("PreparePayResultPaymentPossible"); - -export const codecForPreparePayResultInsufficientBalance = (): Codec<PreparePayResultInsufficientBalance> => - buildCodecForObject<PreparePayResultInsufficientBalance>() - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForAny()) - .property("proposalId", codecForString()) - .property( - "status", - codecForConstString(PreparePayResultType.InsufficientBalance), - ) - .build("PreparePayResultInsufficientBalance"); - -export const codecForPreparePayResultAlreadyConfirmed = (): Codec<PreparePayResultAlreadyConfirmed> => - buildCodecForObject<PreparePayResultAlreadyConfirmed>() - .property( - "status", - codecForConstString(PreparePayResultType.AlreadyConfirmed), - ) - .property("amountEffective", codecForAmountString()) - .property("amountRaw", codecForAmountString()) - .property("paid", codecForBoolean()) - .property("contractTerms", codecForAny()) - .property("contractTermsHash", codecForString()) - .property("proposalId", codecForString()) - .build("PreparePayResultAlreadyConfirmed"); - -export const codecForPreparePayResult = (): Codec<PreparePayResult> => - buildCodecForUnion<PreparePayResult>() - .discriminateOn("status") - .alternative( - PreparePayResultType.AlreadyConfirmed, - codecForPreparePayResultAlreadyConfirmed(), - ) - .alternative( - PreparePayResultType.InsufficientBalance, - codecForPreparePayResultInsufficientBalance(), - ) - .alternative( - PreparePayResultType.PaymentPossible, - codecForPreparePayResultPaymentPossible(), - ) - .build("PreparePayResult"); - -export type PreparePayResult = - | PreparePayResultInsufficientBalance - | PreparePayResultAlreadyConfirmed - | PreparePayResultPaymentPossible; - -export interface PreparePayResultPaymentPossible { - status: PreparePayResultType.PaymentPossible; - proposalId: string; - contractTerms: ContractTerms; - amountRaw: string; - amountEffective: string; -} - -export interface PreparePayResultInsufficientBalance { - status: PreparePayResultType.InsufficientBalance; - proposalId: string; - contractTerms: ContractTerms; - amountRaw: string; -} - -export interface PreparePayResultAlreadyConfirmed { - status: PreparePayResultType.AlreadyConfirmed; - contractTerms: ContractTerms; - paid: boolean; - amountRaw: string; - amountEffective: string; - contractTermsHash: string; - proposalId: string; -} - -export interface BankWithdrawDetails { - selectionDone: boolean; - transferDone: boolean; - amount: AmountJson; - senderWire?: string; - suggestedExchange?: string; - confirmTransferUrl?: string; - wireTypes: string[]; - extractedStatusUrl: string; -} - -export interface AcceptWithdrawalResponse { - reservePub: string; - confirmTransferUrl?: string; -} - -/** - * Details about a purchase, including refund status. - */ -export interface PurchaseDetails { - contractTerms: Record<string, undefined>; - hasRefund: boolean; - totalRefundAmount: AmountJson; - totalRefundAndRefreshFees: AmountJson; -} - -export interface WalletDiagnostics { - walletManifestVersion: string; - walletManifestDisplayVersion: string; - errors: string[]; - firefoxIdbProblem: boolean; - dbOutdated: boolean; -} - -export interface TalerErrorDetails { - code: number; - hint: string; - message: string; - details: unknown; -} - -export interface PlanchetCreationResult { - coinPub: string; - coinPriv: string; - reservePub: string; - denomPubHash: string; - denomPub: string; - blindingKey: string; - withdrawSig: string; - coinEv: string; - coinValue: AmountJson; - coinEvHash: string; -} - -export interface PlanchetCreationRequest { - secretSeed: string; - coinIndex: number; - value: AmountJson; - feeWithdraw: AmountJson; - denomPub: string; - reservePub: string; - reservePriv: string; -} - -/** - * Reasons for why a coin is being refreshed. - */ -export enum RefreshReason { - Manual = "manual", - Pay = "pay", - Refund = "refund", - AbortPay = "abort-pay", - Recoup = "recoup", - BackupRestored = "backup-restored", - Scheduled = "scheduled", -} - -/** - * Wrapper for coin public keys. - */ -export interface CoinPublicKey { - readonly coinPub: string; -} - -/** - * Wrapper for refresh group IDs. - */ -export interface RefreshGroupId { - readonly refreshGroupId: string; -} - -/** - * Private data required to make a deposit permission. - */ -export interface DepositInfo { - exchangeBaseUrl: string; - contractTermsHash: string; - coinPub: string; - coinPriv: string; - spendAmount: AmountJson; - timestamp: Timestamp; - refundDeadline: Timestamp; - merchantPub: string; - feeDeposit: AmountJson; - wireInfoHash: string; - denomPubHash: string; - denomSig: string; -} - -export interface ExchangesListRespose { - exchanges: ExchangeListItem[]; -} - -export interface ExchangeListItem { - exchangeBaseUrl: string; - currency: string; - paytoUris: string[]; -} - -export const codecForExchangeListItem = (): Codec<ExchangeListItem> => - buildCodecForObject<ExchangeListItem>() - .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForString()) - .property("paytoUris", codecForList(codecForString())) - .build("ExchangeListItem"); - -export const codecForExchangesListResponse = (): Codec<ExchangesListRespose> => - buildCodecForObject<ExchangesListRespose>() - .property("exchanges", codecForList(codecForExchangeListItem())) - .build("ExchangesListRespose"); - -export interface AcceptManualWithdrawalResult { - /** - * Payto URIs that can be used to fund the withdrawal. - */ - exchangePaytoUris: string[]; - - /** - * Public key of the newly created reserve. - */ - reservePub: string; -} - -export interface ManualWithdrawalDetails { - /** - * Did the user accept the current version of the exchange's - * terms of service? - */ - tosAccepted: boolean; - - /** - * Amount that the user will transfer to the exchange. - */ - amountRaw: AmountString; - - /** - * Amount that will be added to the user's wallet balance. - */ - amountEffective: AmountString; - - /** - * Ways to pay the exchange. - */ - paytoUris: string[]; -} - -export interface GetExchangeTosResult { - /** - * Markdown version of the current ToS. - */ - tos: string; - - /** - * Version tag of the current ToS. - */ - currentEtag: string; - - /** - * Version tag of the last ToS that the user has accepted, - * if any. - */ - acceptedEtag: string | undefined; -} - -export interface TestPayArgs { - merchantBaseUrl: string; - merchantAuthToken?: string; - amount: string; - summary: string; -} - -export const codecForTestPayArgs = (): Codec<TestPayArgs> => - buildCodecForObject<TestPayArgs>() - .property("merchantBaseUrl", codecForString()) - .property("merchantAuthToken", codecOptional(codecForString())) - .property("amount", codecForString()) - .property("summary", codecForString()) - .build("TestPayArgs"); - -export interface IntegrationTestArgs { - exchangeBaseUrl: string; - bankBaseUrl: string; - merchantBaseUrl: string; - merchantAuthToken?: string; - amountToWithdraw: string; - amountToSpend: string; -} - -export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> => - buildCodecForObject<IntegrationTestArgs>() - .property("exchangeBaseUrl", codecForString()) - .property("bankBaseUrl", codecForString()) - .property("merchantBaseUrl", codecForString()) - .property("merchantAuthToken", codecOptional(codecForString())) - .property("amountToSpend", codecForAmountString()) - .property("amountToWithdraw", codecForAmountString()) - .build("IntegrationTestArgs"); - -export interface AddExchangeRequest { - exchangeBaseUrl: string; -} - -export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => - buildCodecForObject<AddExchangeRequest>() - .property("exchangeBaseUrl", codecForString()) - .build("AddExchangeRequest"); - -export interface ForceExchangeUpdateRequest { - exchangeBaseUrl: string; -} - -export const codecForForceExchangeUpdateRequest = (): Codec<AddExchangeRequest> => - buildCodecForObject<AddExchangeRequest>() - .property("exchangeBaseUrl", codecForString()) - .build("AddExchangeRequest"); - -export interface GetExchangeTosRequest { - exchangeBaseUrl: string; -} - -export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> => - buildCodecForObject<GetExchangeTosRequest>() - .property("exchangeBaseUrl", codecForString()) - .build("GetExchangeTosRequest"); - -export interface AcceptManualWithdrawalRequest { - exchangeBaseUrl: string; - amount: string; -} - -export const codecForAcceptManualWithdrawalRequet = (): Codec<AcceptManualWithdrawalRequest> => - buildCodecForObject<AcceptManualWithdrawalRequest>() - .property("exchangeBaseUrl", codecForString()) - .property("amount", codecForString()) - .build("AcceptManualWithdrawalRequest"); - -export interface GetWithdrawalDetailsForAmountRequest { - exchangeBaseUrl: string; - amount: string; -} - -export interface AcceptBankIntegratedWithdrawalRequest { - talerWithdrawUri: string; - exchangeBaseUrl: string; -} - -export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec<AcceptBankIntegratedWithdrawalRequest> => - buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>() - .property("exchangeBaseUrl", codecForString()) - .property("talerWithdrawUri", codecForString()) - .build("AcceptBankIntegratedWithdrawalRequest"); - -export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec<GetWithdrawalDetailsForAmountRequest> => - buildCodecForObject<GetWithdrawalDetailsForAmountRequest>() - .property("exchangeBaseUrl", codecForString()) - .property("amount", codecForString()) - .build("GetWithdrawalDetailsForAmountRequest"); - -export interface AcceptExchangeTosRequest { - exchangeBaseUrl: string; - etag: string; -} - -export const codecForAcceptExchangeTosRequest = (): Codec<AcceptExchangeTosRequest> => - buildCodecForObject<AcceptExchangeTosRequest>() - .property("exchangeBaseUrl", codecForString()) - .property("etag", codecForString()) - .build("AcceptExchangeTosRequest"); - -export interface ApplyRefundRequest { - talerRefundUri: string; -} - -export const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> => - buildCodecForObject<ApplyRefundRequest>() - .property("talerRefundUri", codecForString()) - .build("ApplyRefundRequest"); - -export interface GetWithdrawalDetailsForUriRequest { - talerWithdrawUri: string; -} - -export const codecForGetWithdrawalDetailsForUri = (): Codec<GetWithdrawalDetailsForUriRequest> => - buildCodecForObject<GetWithdrawalDetailsForUriRequest>() - .property("talerWithdrawUri", codecForString()) - .build("GetWithdrawalDetailsForUriRequest"); - -export interface AbortProposalRequest { - proposalId: string; -} - -export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> => - buildCodecForObject<AbortProposalRequest>() - .property("proposalId", codecForString()) - .build("AbortProposalRequest"); - -export interface PreparePayRequest { - talerPayUri: string; -} - -export const codecForPreparePayRequest = (): Codec<PreparePayRequest> => - buildCodecForObject<PreparePayRequest>() - .property("talerPayUri", codecForString()) - .build("PreparePay"); - -export interface ConfirmPayRequest { - proposalId: string; - sessionId?: string; -} - -export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> => - buildCodecForObject<ConfirmPayRequest>() - .property("proposalId", codecForString()) - .property("sessionId", codecOptional(codecForString())) - .build("ConfirmPay"); - -export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; - -export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification; - -export interface CoreApiNotification { - type: "notification"; - payload: unknown; -} - -export interface CoreApiResponseSuccess { - // To distinguish the message from notifications - type: "response"; - operation: string; - id: string; - result: unknown; -} - -export interface CoreApiResponseError { - // To distinguish the message from notifications - type: "error"; - operation: string; - id: string; - error: TalerErrorDetails; -} - -export interface WithdrawTestBalanceRequest { - amount: string; - bankBaseUrl: string; - exchangeBaseUrl: string; -} - -export const withdrawTestBalanceDefaults = { - amount: "TESTKUDOS:10", - bankBaseUrl: "https://bank.test.taler.net/", - exchangeBaseUrl: "https://exchange.test.taler.net/", -}; - -/** - * Request to the crypto worker to make a sync signature. - */ -export interface MakeSyncSignatureRequest { - accountPriv: string; - oldHash: string | undefined; - newHash: string; -} - -/** - * Strategy for loading recovery information. - */ -export enum RecoveryMergeStrategy { - /** - * Keep the local wallet root key, import and take over providers. - */ - Ours = "ours", - - /** - * Migrate to the wallet root key from the recovery information. - */ - Theirs = "theirs", -} - -/** - * Load recovery information into the wallet. - */ -export interface RecoveryLoadRequest { - recovery: BackupRecovery; - strategy?: RecoveryMergeStrategy; -} - -export const codecForWithdrawTestBalance = (): Codec<WithdrawTestBalanceRequest> => - buildCodecForObject<WithdrawTestBalanceRequest>() - .property("amount", codecForString()) - .property("bankBaseUrl", codecForString()) - .property("exchangeBaseUrl", codecForString()) - .build("WithdrawTestBalanceRequest"); - -export interface ApplyRefundResponse { - contractTermsHash: string; - - proposalId: string; - - amountEffectivePaid: AmountString; - - amountRefundGranted: AmountString; - - amountRefundGone: AmountString; - - pendingAtExchange: boolean; - - info: OrderShortInfo; -} - -export const codecForApplyRefundResponse = (): Codec<ApplyRefundResponse> => - buildCodecForObject<ApplyRefundResponse>() - .property("amountEffectivePaid", codecForAmountString()) - .property("amountRefundGone", codecForAmountString()) - .property("amountRefundGranted", codecForAmountString()) - .property("contractTermsHash", codecForString()) - .property("pendingAtExchange", codecForBoolean()) - .property("proposalId", codecForString()) - .property("info", codecForOrderShortInfo()) - .build("ApplyRefundResponse"); - -export interface SetCoinSuspendedRequest { - coinPub: string; - suspended: boolean; -} - -export const codecForSetCoinSuspendedRequest = (): Codec<SetCoinSuspendedRequest> => - buildCodecForObject<SetCoinSuspendedRequest>() - .property("coinPub", codecForString()) - .property("suspended", codecForBoolean()) - .build("SetCoinSuspendedRequest"); - -export interface ForceRefreshRequest { - coinPubList: string[]; -} - -export const codecForForceRefreshRequest = (): Codec<ForceRefreshRequest> => - buildCodecForObject<ForceRefreshRequest>() - .property("coinPubList", codecForList(codecForString())) - .build("ForceRefreshRequest"); - -export interface PrepareTipRequest { - talerTipUri: string; -} - -export const codecForPrepareTipRequest = (): Codec<PrepareTipRequest> => - buildCodecForObject<PrepareTipRequest>() - .property("talerTipUri", codecForString()) - .build("PrepareTipRequest"); - -export interface AcceptTipRequest { - walletTipId: string; -} - -export const codecForAcceptTipRequest = (): Codec<AcceptTipRequest> => - buildCodecForObject<AcceptTipRequest>() - .property("walletTipId", codecForString()) - .build("AcceptTipRequest"); - -export interface AbortPayWithRefundRequest { - proposalId: string; -} - -export const codecForAbortPayWithRefundRequest = (): Codec<AbortPayWithRefundRequest> => - buildCodecForObject<AbortPayWithRefundRequest>() - .property("proposalId", codecForString()) - .build("AbortPayWithRefundRequest"); - -export interface CreateDepositGroupRequest { - depositPaytoUri: string; - amount: string; -} - -export const codecForCreateDepositGroupRequest = (): Codec<CreateDepositGroupRequest> => - buildCodecForObject<CreateDepositGroupRequest>() - .property("amount", codecForAmountString()) - .property("depositPaytoUri", codecForString()) - .build("CreateDepositGroupRequest"); - -export interface CreateDepositGroupResponse { - depositGroupId: string; -} - -export interface TrackDepositGroupRequest { - depositGroupId: string; -} - -export interface TrackDepositGroupResponse { - responses: { - status: number; - body: any; - }[]; -} - -export const codecForTrackDepositGroupRequest = (): Codec<TrackDepositGroupRequest> => - buildCodecForObject<TrackDepositGroupRequest>() - .property("depositGroupId", codecForAmountString()) - .build("TrackDepositGroupRequest"); - -export interface WithdrawUriInfoResponse { - amount: AmountString; - defaultExchangeBaseUrl?: string; - possibleExchanges: ExchangeListItem[]; -} - -export const codecForWithdrawUriInfoResponse = (): Codec<WithdrawUriInfoResponse> => - buildCodecForObject<WithdrawUriInfoResponse>() - .property("amount", codecForAmountString()) - .property("defaultExchangeBaseUrl", codecOptional(codecForString())) - .property("possibleExchanges", codecForList(codecForExchangeListItem())) - .build("WithdrawUriInfoResponse"); diff --git a/packages/taler-wallet-core/src/util/RequestThrottler.ts b/packages/taler-wallet-core/src/util/RequestThrottler.ts @@ -25,7 +25,7 @@ import { getTimestampNow, timestampDifference, timestampCmp, -} from "../util/time"; +} from "@gnu-taler/taler-util"; import { URL } from "./url"; import { Logger } from "./logging"; diff --git a/packages/taler-wallet-core/src/util/amounts-test.ts b/packages/taler-wallet-core/src/util/amounts-test.ts @@ -1,140 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import test from "ava"; - -import { Amounts, AmountJson } from "../util/amounts"; - -const jAmt = ( - value: number, - fraction: number, - currency: string, -): AmountJson => ({ value, fraction, currency }); - -const sAmt = (s: string): AmountJson => Amounts.parseOrThrow(s); - -test("amount addition (simple)", (t) => { - const a1 = jAmt(1, 0, "EUR"); - const a2 = jAmt(1, 0, "EUR"); - const a3 = jAmt(2, 0, "EUR"); - t.true(0 === Amounts.cmp(Amounts.add(a1, a2).amount, a3)); - t.pass(); -}); - -test("amount addition (saturation)", (t) => { - const a1 = jAmt(1, 0, "EUR"); - const res = Amounts.add(jAmt(Amounts.maxAmountValue, 0, "EUR"), a1); - t.true(res.saturated); - t.pass(); -}); - -test("amount subtraction (simple)", (t) => { - const a1 = jAmt(2, 5, "EUR"); - const a2 = jAmt(1, 0, "EUR"); - const a3 = jAmt(1, 5, "EUR"); - t.true(0 === Amounts.cmp(Amounts.sub(a1, a2).amount, a3)); - t.pass(); -}); - -test("amount subtraction (saturation)", (t) => { - const a1 = jAmt(0, 0, "EUR"); - const a2 = jAmt(1, 0, "EUR"); - let res = Amounts.sub(a1, a2); - t.true(res.saturated); - res = Amounts.sub(a1, a1); - t.true(!res.saturated); - t.pass(); -}); - -test("amount comparison", (t) => { - t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(1, 0, "EUR")), 0); - t.is(Amounts.cmp(jAmt(1, 1, "EUR"), jAmt(1, 0, "EUR")), 1); - t.is(Amounts.cmp(jAmt(1, 1, "EUR"), jAmt(1, 2, "EUR")), -1); - t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(0, 0, "EUR")), 1); - t.is(Amounts.cmp(jAmt(0, 0, "EUR"), jAmt(1, 0, "EUR")), -1); - t.is(Amounts.cmp(jAmt(1, 0, "EUR"), jAmt(0, 100000000, "EUR")), 0); - t.throws(() => Amounts.cmp(jAmt(1, 0, "FOO"), jAmt(1, 0, "BAR"))); - t.pass(); -}); - -test("amount parsing", (t) => { - t.is( - Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:0"), jAmt(0, 0, "TESTKUDOS")), - 0, - ); - t.is( - Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:10"), jAmt(10, 0, "TESTKUDOS")), - 0, - ); - t.is( - Amounts.cmp( - Amounts.parseOrThrow("TESTKUDOS:0.1"), - jAmt(0, 10000000, "TESTKUDOS"), - ), - 0, - ); - t.is( - Amounts.cmp( - Amounts.parseOrThrow("TESTKUDOS:0.00000001"), - jAmt(0, 1, "TESTKUDOS"), - ), - 0, - ); - t.is( - Amounts.cmp( - Amounts.parseOrThrow("TESTKUDOS:4503599627370496.99999999"), - jAmt(4503599627370496, 99999999, "TESTKUDOS"), - ), - 0, - ); - t.throws(() => Amounts.parseOrThrow("foo:")); - t.throws(() => Amounts.parseOrThrow("1.0")); - t.throws(() => Amounts.parseOrThrow("42")); - t.throws(() => Amounts.parseOrThrow(":1.0")); - t.throws(() => Amounts.parseOrThrow(":42")); - t.throws(() => Amounts.parseOrThrow("EUR:.42")); - t.throws(() => Amounts.parseOrThrow("EUR:42.")); - t.throws(() => Amounts.parseOrThrow("TESTKUDOS:4503599627370497.99999999")); - t.is( - Amounts.cmp( - Amounts.parseOrThrow("TESTKUDOS:0.99999999"), - jAmt(0, 99999999, "TESTKUDOS"), - ), - 0, - ); - t.throws(() => Amounts.parseOrThrow("TESTKUDOS:0.999999991")); - t.pass(); -}); - -test("amount stringification", (t) => { - t.is(Amounts.stringify(jAmt(0, 0, "TESTKUDOS")), "TESTKUDOS:0"); - t.is(Amounts.stringify(jAmt(4, 94000000, "TESTKUDOS")), "TESTKUDOS:4.94"); - t.is(Amounts.stringify(jAmt(0, 10000000, "TESTKUDOS")), "TESTKUDOS:0.1"); - t.is(Amounts.stringify(jAmt(0, 1, "TESTKUDOS")), "TESTKUDOS:0.00000001"); - t.is(Amounts.stringify(jAmt(5, 0, "TESTKUDOS")), "TESTKUDOS:5"); - // denormalized - t.is(Amounts.stringify(jAmt(1, 100000000, "TESTKUDOS")), "TESTKUDOS:2"); - t.pass(); -}); - -test("amount multiplication", (t) => { - t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 0).amount), "EUR:0"); - t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 1).amount), "EUR:1.11"); - t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 2).amount), "EUR:2.22"); - t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 3).amount), "EUR:3.33"); - t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 4).amount), "EUR:4.44"); - t.is(Amounts.stringify(Amounts.mult(sAmt("EUR:1.11"), 5).amount), "EUR:5.55"); -}); diff --git a/packages/taler-wallet-core/src/util/amounts.ts b/packages/taler-wallet-core/src/util/amounts.ts @@ -1,423 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Types and helper functions for dealing with Taler amounts. - */ - -/** - * Imports. - */ -import { - buildCodecForObject, - codecForString, - codecForNumber, - Codec, -} from "./codec"; -import { AmountString } from "../types/talerTypes"; - -/** - * Number of fractional units that one value unit represents. - */ -export const fractionalBase = 1e8; - -/** - * How many digits behind the comma are required to represent the - * fractional value in human readable decimal format? Must match - * lg(fractionalBase) - */ -export const fractionalLength = 8; - -/** - * Maximum allowed value field of an amount. - */ -export const maxAmountValue = 2 ** 52; - -/** - * Non-negative financial amount. Fractional values are expressed as multiples - * of 1e-8. - */ -export interface AmountJson { - /** - * Value, must be an integer. - */ - readonly value: number; - - /** - * Fraction, must be an integer. Represent 1/1e8 of a unit. - */ - readonly fraction: number; - - /** - * Currency of the amount. - */ - readonly currency: string; -} - -export const codecForAmountJson = (): Codec<AmountJson> => - buildCodecForObject<AmountJson>() - .property("currency", codecForString()) - .property("value", codecForNumber()) - .property("fraction", codecForNumber()) - .build("AmountJson"); - -export const codecForAmountString = (): Codec<AmountString> => codecForString(); - -/** - * Result of a possibly overflowing operation. - */ -export interface Result { - /** - * Resulting, possibly saturated amount. - */ - amount: AmountJson; - /** - * Was there an over-/underflow? - */ - saturated: boolean; -} - -/** - * Get an amount that represents zero units of a currency. - */ -export function getZero(currency: string): AmountJson { - return { - currency, - fraction: 0, - value: 0, - }; -} - -export type AmountLike = AmountString | AmountJson; - -export function jsonifyAmount(amt: AmountLike): AmountJson { - if (typeof amt === "string") { - return parseOrThrow(amt); - } - return amt; -} - -export function sum(amounts: AmountLike[]): Result { - if (amounts.length <= 0) { - throw Error("can't sum zero amounts"); - } - const jsonAmounts = amounts.map((x) => jsonifyAmount(x)); - return add(jsonAmounts[0], ...jsonAmounts.slice(1)); -} - -/** - * Add two amounts. Return the result and whether - * the addition overflowed. The overflow is always handled - * by saturating and never by wrapping. - * - * Throws when currencies don't match. - */ -export function add(first: AmountJson, ...rest: AmountJson[]): Result { - const currency = first.currency; - let value = first.value + Math.floor(first.fraction / fractionalBase); - if (value > maxAmountValue) { - return { - amount: { currency, value: maxAmountValue, fraction: fractionalBase - 1 }, - saturated: true, - }; - } - let fraction = first.fraction % fractionalBase; - for (const x of rest) { - if (x.currency !== currency) { - throw Error(`Mismatched currency: ${x.currency} and ${currency}`); - } - - value = - value + x.value + Math.floor((fraction + x.fraction) / fractionalBase); - fraction = Math.floor((fraction + x.fraction) % fractionalBase); - if (value > maxAmountValue) { - return { - amount: { - currency, - value: maxAmountValue, - fraction: fractionalBase - 1, - }, - saturated: true, - }; - } - } - return { amount: { currency, value, fraction }, saturated: false }; -} - -/** - * Subtract two amounts. Return the result and whether - * the subtraction overflowed. The overflow is always handled - * by saturating and never by wrapping. - * - * Throws when currencies don't match. - */ -export function sub(a: AmountJson, ...rest: AmountJson[]): Result { - const currency = a.currency; - let value = a.value; - let fraction = a.fraction; - - for (const b of rest) { - if (b.currency !== currency) { - throw Error(`Mismatched currency: ${b.currency} and ${currency}`); - } - if (fraction < b.fraction) { - if (value < 1) { - return { amount: { currency, value: 0, fraction: 0 }, saturated: true }; - } - value--; - fraction += fractionalBase; - } - console.assert(fraction >= b.fraction); - fraction -= b.fraction; - if (value < b.value) { - return { amount: { currency, value: 0, fraction: 0 }, saturated: true }; - } - value -= b.value; - } - - return { amount: { currency, value, fraction }, saturated: false }; -} - -/** - * Compare two amounts. Returns 0 when equal, -1 when a < b - * and +1 when a > b. Throws when currencies don't match. - */ -export function cmp(a: AmountLike, b: AmountLike): -1 | 0 | 1 { - a = jsonifyAmount(a); - b = jsonifyAmount(b); - if (a.currency !== b.currency) { - throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); - } - const av = a.value + Math.floor(a.fraction / fractionalBase); - const af = a.fraction % fractionalBase; - const bv = b.value + Math.floor(b.fraction / fractionalBase); - const bf = b.fraction % fractionalBase; - switch (true) { - case av < bv: - return -1; - case av > bv: - return 1; - case af < bf: - return -1; - case af > bf: - return 1; - case af === bf: - return 0; - default: - throw Error("assertion failed"); - } -} - -/** - * Create a copy of an amount. - */ -export function copy(a: AmountJson): AmountJson { - return { - currency: a.currency, - fraction: a.fraction, - value: a.value, - }; -} - -/** - * Divide an amount. Throws on division by zero. - */ -export function divide(a: AmountJson, n: number): AmountJson { - if (n === 0) { - throw Error(`Division by 0`); - } - if (n === 1) { - return { value: a.value, fraction: a.fraction, currency: a.currency }; - } - const r = a.value % n; - return { - currency: a.currency, - fraction: Math.floor((r * fractionalBase + a.fraction) / n), - value: Math.floor(a.value / n), - }; -} - -/** - * Check if an amount is non-zero. - */ -export function isNonZero(a: AmountJson): boolean { - return a.value > 0 || a.fraction > 0; -} - -export function isZero(a: AmountLike): boolean { - a = jsonifyAmount(a); - return a.value === 0 && a.fraction === 0; -} - -/** - * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct. - */ -export function parse(s: string): AmountJson | undefined { - const res = s.match(/^([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?$/); - if (!res) { - return undefined; - } - const tail = res[3] || ".0"; - if (tail.length > fractionalLength + 1) { - return undefined; - } - const value = Number.parseInt(res[2]); - if (value > maxAmountValue) { - return undefined; - } - return { - currency: res[1], - fraction: Math.round(fractionalBase * Number.parseFloat(tail)), - value, - }; -} - -/** - * Parse amount in standard string form (like 'EUR:20.5'), - * throw if the input is not a valid amount. - */ -export function parseOrThrow(s: string): AmountJson { - const res = parse(s); - if (!res) { - throw Error(`Can't parse amount: "${s}"`); - } - return res; -} - -/** - * Convert a float to a Taler amount. - * Loss of precision possible. - */ -export function fromFloat(floatVal: number, currency: string): AmountJson { - return { - currency, - fraction: Math.floor((floatVal - Math.floor(floatVal)) * fractionalBase), - value: Math.floor(floatVal), - }; -} - -/** - * Convert to standard human-readable string representation that's - * also used in JSON formats. - */ -export function stringify(a: AmountLike): string { - a = jsonifyAmount(a); - const av = a.value + Math.floor(a.fraction / fractionalBase); - const af = a.fraction % fractionalBase; - let s = av.toString(); - - if (af) { - s = s + "."; - let n = af; - for (let i = 0; i < fractionalLength; i++) { - if (!n) { - break; - } - s = s + Math.floor((n / fractionalBase) * 10).toString(); - n = (n * 10) % fractionalBase; - } - } - - return `${a.currency}:${s}`; -} - -/** - * Check if the argument is a valid amount in string form. - */ -function check(a: any): boolean { - if (typeof a !== "string") { - return false; - } - try { - const parsedAmount = parse(a); - return !!parsedAmount; - } catch { - return false; - } -} - -function mult(a: AmountJson, n: number): Result { - if (!Number.isInteger(n)) { - throw Error("amount can only be multipied by an integer"); - } - if (n < 0) { - throw Error("amount can only be multiplied by a positive integer"); - } - if (n == 0) { - return { amount: getZero(a.currency), saturated: false }; - } - let x = a; - let acc = getZero(a.currency); - while (n > 1) { - if (n % 2 == 0) { - n = n / 2; - } else { - n = (n - 1) / 2; - const r2 = add(acc, x); - if (r2.saturated) { - return r2; - } - acc = r2.amount; - } - const r2 = add(x, x); - if (r2.saturated) { - return r2; - } - x = r2.amount; - } - return add(acc, x); -} - -function max(a: AmountLike, b: AmountLike): AmountJson { - const cr = Amounts.cmp(a, b); - if (cr >= 0) { - return jsonifyAmount(a); - } else { - return jsonifyAmount(b); - } -} - -function min(a: AmountLike, b: AmountLike): AmountJson { - const cr = Amounts.cmp(a, b); - if (cr >= 0) { - return jsonifyAmount(b); - } else { - return jsonifyAmount(a); - } -} - - -// Export all amount-related functions here for better IDE experience. -export const Amounts = { - stringify: stringify, - parse: parse, - parseOrThrow: parseOrThrow, - cmp: cmp, - add: add, - sum: sum, - sub: sub, - mult: mult, - max: max, - min: min, - check: check, - getZero: getZero, - isZero: isZero, - maxAmountValue: maxAmountValue, - fromFloat: fromFloat, - copy: copy, - fractionalBase: fractionalBase, - divide: divide, -}; diff --git a/packages/taler-wallet-core/src/util/coinSelection-test.ts b/packages/taler-wallet-core/src/util/coinSelection-test.ts @@ -17,8 +17,8 @@ /** * Imports. */ +import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import test from "ava"; -import { AmountJson, Amounts } from ".."; import { AvailableCoinInfo, selectPayCoins } from "./coinSelection"; function a(x: string): AmountJson { diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts @@ -23,7 +23,7 @@ /** * Imports. */ -import { AmountJson, Amounts } from "./amounts"; +import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { strcmp } from "./helpers"; /** diff --git a/packages/taler-wallet-core/src/util/helpers.ts b/packages/taler-wallet-core/src/util/helpers.ts @@ -21,8 +21,7 @@ /** * Imports. */ -import { AmountJson } from "./amounts"; -import * as Amounts from "./amounts"; +import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { URL } from "./url"; /** diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts @@ -24,19 +24,18 @@ /** * Imports */ -import { Codec } from "./codec"; import { OperationFailedError, makeErrorDetails } from "../operations/errors"; -import { TalerErrorCode } from "../TalerErrorCode"; import { Logger } from "./logging"; import { Duration, Timestamp, getTimestampNow, timestampAddDuration, - timestampMin, timestampMax, -} from "./time"; -import { TalerErrorDetails } from ".."; + TalerErrorDetails, + Codec, +} from "@gnu-taler/taler-util"; +import { TalerErrorCode } from "@gnu-taler/taler-util"; const logger = new Logger("http.ts"); diff --git a/packages/taler-wallet-core/src/util/libtoolVersion-test.ts b/packages/taler-wallet-core/src/util/libtoolVersion-test.ts @@ -1,48 +0,0 @@ -/* - This file is part of TALER - (C) 2017 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import * as LibtoolVersion from "./libtoolVersion"; - -import test from "ava"; - -test("version comparison", (t) => { - t.deepEqual(LibtoolVersion.compare("0:0:0", "0:0:0"), { - compatible: true, - currentCmp: 0, - }); - t.deepEqual(LibtoolVersion.compare("0:0:0", ""), undefined); - t.deepEqual(LibtoolVersion.compare("foo", "0:0:0"), undefined); - t.deepEqual(LibtoolVersion.compare("0:0:0", "1:0:1"), { - compatible: true, - currentCmp: -1, - }); - t.deepEqual(LibtoolVersion.compare("0:0:0", "1:5:1"), { - compatible: true, - currentCmp: -1, - }); - t.deepEqual(LibtoolVersion.compare("0:0:0", "1:5:0"), { - compatible: false, - currentCmp: -1, - }); - t.deepEqual(LibtoolVersion.compare("1:0:0", "0:5:0"), { - compatible: false, - currentCmp: 1, - }); - t.deepEqual(LibtoolVersion.compare("1:0:1", "1:5:1"), { - compatible: true, - currentCmp: 0, - }); -}); diff --git a/packages/taler-wallet-core/src/util/payto.ts b/packages/taler-wallet-core/src/util/payto.ts @@ -1,71 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import { URLSearchParams } from "./url"; - -interface PaytoUri { - targetType: string; - targetPath: string; - params: { [name: string]: string }; -} - -const paytoPfx = "payto://"; - -/** - * Add query parameters to a payto URI - */ -export function addPaytoQueryParams( - s: string, - params: { [name: string]: string }, -): string { - const [acct, search] = s.slice(paytoPfx.length).split("?"); - const searchParams = new URLSearchParams(search || ""); - for (const k of Object.keys(params)) { - searchParams.set(k, params[k]); - } - return paytoPfx + acct + "?" + searchParams.toString(); -} - -export function parsePaytoUri(s: string): PaytoUri | undefined { - if (!s.startsWith(paytoPfx)) { - return undefined; - } - - const [acct, search] = s.slice(paytoPfx.length).split("?"); - - const firstSlashPos = acct.indexOf("/"); - - if (firstSlashPos === -1) { - return undefined; - } - - const targetType = acct.slice(0, firstSlashPos); - const targetPath = acct.slice(firstSlashPos + 1); - - const params: { [k: string]: string } = {}; - - const searchParams = new URLSearchParams(search || ""); - - searchParams.forEach((v, k) => { - params[v] = k; - }); - - return { - targetPath, - targetType, - params, - }; -} diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts @@ -21,7 +21,7 @@ /** * Imports. */ -import { Timestamp, Duration, getTimestampNow } from "./time"; +import { Timestamp, Duration, getTimestampNow } from "@gnu-taler/taler-util"; export interface RetryInfo { firstTry: Timestamp; diff --git a/packages/taler-wallet-core/src/util/testvectors.ts b/packages/taler-wallet-core/src/util/testvectors.ts @@ -1,36 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Imports - */ -import { - setupRefreshPlanchet, - encodeCrock, - getRandomBytes, -} from "../crypto/talerCrypto"; - -export function printTestVectors() { - const secretSeed = getRandomBytes(64); - const coinIndex = Math.ceil(Math.random() * 100); - const p = setupRefreshPlanchet(secretSeed, coinIndex); - console.log("setupRefreshPlanchet"); - console.log(` (in) secret seed: ${encodeCrock(secretSeed)}`); - console.log(` (in) coin index: ${coinIndex}`); - console.log(` (out) blinding secret: ${encodeCrock(p.bks)}`); - console.log(` (out) coin priv: ${encodeCrock(p.coinPriv)}`); - console.log(` (out) coin pub: ${encodeCrock(p.coinPub)}`); -} diff --git a/packages/taler-wallet-core/src/util/time.ts b/packages/taler-wallet-core/src/util/time.ts @@ -1,261 +0,0 @@ -import { Codec, renderContext, Context } from "./codec"; - -/* - This file is part of GNU Taler - (C) 2017-2019 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Helpers for relative and absolute time. - */ - -export class Timestamp { - /** - * Timestamp in milliseconds. - */ - readonly t_ms: number | "never"; -} - -export interface Duration { - /** - * Duration in milliseconds. - */ - readonly d_ms: number | "forever"; -} - -let timeshift = 0; - -export function setDangerousTimetravel(dt: number): void { - timeshift = dt; -} - -export function getTimestampNow(): Timestamp { - return { - t_ms: new Date().getTime() + timeshift, - }; -} - -export function isTimestampExpired(t: Timestamp) { - return timestampCmp(t, getTimestampNow()) <= 0; -} - -export function getDurationRemaining( - deadline: Timestamp, - now = getTimestampNow(), -): Duration { - if (deadline.t_ms === "never") { - return { d_ms: "forever" }; - } - if (now.t_ms === "never") { - throw Error("invalid argument for 'now'"); - } - if (deadline.t_ms < now.t_ms) { - return { d_ms: 0 }; - } - return { d_ms: deadline.t_ms - now.t_ms }; -} - -export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: t2.t_ms }; - } - if (t2.t_ms === "never") { - return { t_ms: t2.t_ms }; - } - return { t_ms: Math.min(t1.t_ms, t2.t_ms) }; -} - -export function timestampMax(t1: Timestamp, t2: Timestamp): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: "never" }; - } - if (t2.t_ms === "never") { - return { t_ms: "never" }; - } - return { t_ms: Math.max(t1.t_ms, t2.t_ms) }; -} - -const SECONDS = 1000; -const MINUTES = SECONDS * 60; -const HOURS = MINUTES * 60; -const DAYS = HOURS * 24; -const MONTHS = DAYS * 30; -const YEARS = DAYS * 365; - -export function durationFromSpec(spec: { - seconds?: number; - minutes?: number; - hours?: number; - days?: number; - months?: number; - years?: number; -}): Duration { - let d_ms = 0; - d_ms += (spec.seconds ?? 0) * SECONDS; - d_ms += (spec.minutes ?? 0) * MINUTES; - d_ms += (spec.hours ?? 0) * HOURS; - d_ms += (spec.days ?? 0) * DAYS; - d_ms += (spec.months ?? 0) * MONTHS; - d_ms += (spec.years ?? 0) * YEARS; - return { d_ms }; -} - -/** - * Truncate a timestamp so that that it represents a multiple - * of seconds. The timestamp is always rounded down. - */ -export function timestampTruncateToSecond(t1: Timestamp): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: "never" }; - } - return { - t_ms: Math.floor(t1.t_ms / 1000) * 1000, - }; -} - -export function durationMin(d1: Duration, d2: Duration): Duration { - if (d1.d_ms === "forever") { - return { d_ms: d2.d_ms }; - } - if (d2.d_ms === "forever") { - return { d_ms: d2.d_ms }; - } - return { d_ms: Math.min(d1.d_ms, d2.d_ms) }; -} - -export function durationMax(d1: Duration, d2: Duration): Duration { - if (d1.d_ms === "forever") { - return { d_ms: "forever" }; - } - if (d2.d_ms === "forever") { - return { d_ms: "forever" }; - } - return { d_ms: Math.max(d1.d_ms, d2.d_ms) }; -} - -export function durationMul(d: Duration, n: number): Duration { - if (d.d_ms === "forever") { - return { d_ms: "forever" }; - } - return { d_ms: Math.round(d.d_ms * n) }; -} - -export function durationAdd(d1: Duration, d2: Duration): Duration { - if (d1.d_ms === "forever" || d2.d_ms === "forever") { - return { d_ms: "forever" }; - } - return { d_ms: d1.d_ms + d2.d_ms }; -} - -export function timestampCmp(t1: Timestamp, t2: Timestamp): number { - if (t1.t_ms === "never") { - if (t2.t_ms === "never") { - return 0; - } - return 1; - } - if (t2.t_ms === "never") { - return -1; - } - if (t1.t_ms == t2.t_ms) { - return 0; - } - if (t1.t_ms > t2.t_ms) { - return 1; - } - return -1; -} - -export function timestampAddDuration(t1: Timestamp, d: Duration): Timestamp { - if (t1.t_ms === "never" || d.d_ms === "forever") { - return { t_ms: "never" }; - } - return { t_ms: t1.t_ms + d.d_ms }; -} - -export function timestampSubtractDuraction( - t1: Timestamp, - d: Duration, -): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: "never" }; - } - if (d.d_ms === "forever") { - return { t_ms: 0 }; - } - return { t_ms: Math.max(0, t1.t_ms - d.d_ms) }; -} - -export function stringifyTimestamp(t: Timestamp): string { - if (t.t_ms === "never") { - return "never"; - } - return new Date(t.t_ms).toISOString(); -} - -export function timestampDifference(t1: Timestamp, t2: Timestamp): Duration { - if (t1.t_ms === "never") { - return { d_ms: "forever" }; - } - if (t2.t_ms === "never") { - return { d_ms: "forever" }; - } - return { d_ms: Math.abs(t1.t_ms - t2.t_ms) }; -} - -export function timestampIsBetween( - t: Timestamp, - start: Timestamp, - end: Timestamp, -): boolean { - if (timestampCmp(t, start) < 0) { - return false; - } - if (timestampCmp(t, end) > 0) { - return false; - } - return true; -} - -export const codecForTimestamp: Codec<Timestamp> = { - decode(x: any, c?: Context): Timestamp { - const t_ms = x.t_ms; - if (typeof t_ms === "string") { - if (t_ms === "never") { - return { t_ms: "never" }; - } - throw Error(`expected timestamp at ${renderContext(c)}`); - } - if (typeof t_ms === "number") { - return { t_ms }; - } - throw Error(`expected timestamp at ${renderContext(c)}`); - }, -}; - -export const codecForDuration: Codec<Duration> = { - decode(x: any, c?: Context): Duration { - const d_ms = x.d_ms; - if (typeof d_ms === "string") { - if (d_ms === "forever") { - return { d_ms: "forever" }; - } - throw Error(`expected duration at ${renderContext(c)}`); - } - if (typeof d_ms === "number") { - return { d_ms }; - } - throw Error(`expected duration at ${renderContext(c)}`); - }, -}; diff --git a/packages/taler-wallet-core/src/util/timer.ts b/packages/taler-wallet-core/src/util/timer.ts @@ -24,7 +24,7 @@ /** * Imports. */ -import { Duration } from "./time"; +import { Duration } from "@gnu-taler/taler-util"; import { Logger } from "./logging"; const logger = new Logger("timer.ts"); diff --git a/packages/taler-wallet-core/src/util/wire.ts b/packages/taler-wallet-core/src/util/wire.ts @@ -1,51 +0,0 @@ -/* - This file is part of TALER - (C) 2017 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Display and manipulate wire information. - * - * Right now, all types are hard-coded. In the future, there might be plugins / configurable - * methods or support for the "payto://" URI scheme. - */ - -/** - * Imports. - */ -import * as i18n from "../i18n"; - -/** - * Short summary of the wire information. - * - * Might abbreviate and return the same summary for different - * wire details. - */ -export function summarizeWire(w: any): string { - if (!w.type) { - return i18n.str`Invalid Wire`; - } - switch (w.type.toLowerCase()) { - case "test": - if (!w.account_number && w.account_number !== 0) { - return i18n.str`Invalid Test Wire Detail`; - } - if (!w.bank_uri) { - return i18n.str`Invalid Test Wire Detail`; - } - return i18n.str`Test Wire Acct #${w.account_number} on ${w.bank_uri}`; - default: - return i18n.str`Unknown Wire Detail`; - } -} diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -22,13 +22,12 @@ /** * Imports. */ -import { codecForAny, TalerErrorCode } from "."; +import { BackupRecovery, codecForAny, TalerErrorCode } from "@gnu-taler/taler-util"; import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi"; import { addBackupProvider, AddBackupProviderRequest, BackupInfo, - BackupRecovery, codecForAddBackupProviderRequest, exportBackupEncrypted, getBackupInfo, @@ -106,19 +105,19 @@ import { ReserveRecord, ReserveRecordStatus, Stores, -} from "./types/dbTypes"; -import { NotificationType, WalletNotification } from "./types/notifications"; +} from "./db.js"; +import { NotificationType, WalletNotification } from "@gnu-taler/taler-util"; import { PendingOperationInfo, PendingOperationsResponse, PendingOperationType, -} from "./types/pendingTypes"; -import { CoinDumpJson } from "./types/talerTypes"; +} from "./pending-types.js"; +import { CoinDumpJson } from "@gnu-taler/taler-util"; import { codecForTransactionsRequest, TransactionsRequest, TransactionsResponse, -} from "./types/transactionsTypes"; +} from "@gnu-taler/taler-util"; import { AcceptManualWithdrawalResult, AcceptWithdrawalResponse, @@ -166,15 +165,15 @@ import { TrackDepositGroupResponse, WithdrawTestBalanceRequest, WithdrawUriInfoResponse, -} from "./types/walletTypes"; -import { AmountJson, Amounts } from "./util/amounts"; +} from "@gnu-taler/taler-util"; +import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { assertUnreachable } from "./util/assertUnreachable"; import { AsyncOpMemoSingle } from "./util/asyncMemo"; import { HttpRequestLibrary } from "./util/http"; import { Logger } from "./util/logging"; import { AsyncCondition } from "./util/promiseUtils"; import { Database } from "./util/query"; -import { Duration, durationMin } from "./util/time"; +import { Duration, durationMin } from "@gnu-taler/taler-util"; import { TimerGroup } from "./util/timer"; const builtinCurrencies: CurrencyRecord[] = [ diff --git a/packages/taler-wallet-core/tsconfig.json b/packages/taler-wallet-core/tsconfig.json @@ -26,7 +26,10 @@ "references": [ { "path": "../idb-bridge/" + }, + { + "path": "../taler-util/" } ], - "include": ["src/**/*"], + "include": ["src/**/*"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml @@ -35,6 +35,24 @@ importers: specifiers: '@types/node': ^14.14.22 typescript: ^4.1.3 + packages/taler-util: + dependencies: + tslib: 2.1.0 + devDependencies: + '@types/node': 14.14.34 + ava: 3.15.0 + esbuild: 0.9.2 + prettier: 2.2.1 + rimraf: 3.0.2 + typescript: 4.2.3 + specifiers: + '@types/node': ^14.14.22 + ava: ^3.15.0 + esbuild: ^0.9.2 + prettier: ^2.2.1 + rimraf: ^3.0.2 + tslib: ^2.1.0 + typescript: ^4.2.3 packages/taler-wallet-android: dependencies: '@gnu-taler/taler-wallet-core': link:../taler-wallet-core @@ -67,6 +85,7 @@ importers: typescript: ^4.1.3 packages/taler-wallet-cli: dependencies: + '@gnu-taler/taler-util': link:../taler-util '@gnu-taler/taler-wallet-core': link:../taler-wallet-core '@types/minimatch': 3.0.3 axios: 0.21.1 @@ -88,6 +107,7 @@ importers: typedoc: 0.20.16_typescript@4.1.3 typescript: 4.1.3 specifiers: + '@gnu-taler/taler-util': workspace:* '@gnu-taler/taler-wallet-core': workspace:* '@rollup/plugin-commonjs': ^17.0.0 '@rollup/plugin-json': ^4.1.0 @@ -110,6 +130,7 @@ importers: packages/taler-wallet-core: dependencies: '@gnu-taler/idb-bridge': link:../idb-bridge + '@gnu-taler/taler-util': link:../taler-util '@types/node': 14.14.22 axios: 0.21.1 big-integer: 1.6.48 @@ -144,6 +165,7 @@ importers: '@ava/typescript': ^1.1.1 '@gnu-taler/idb-bridge': workspace:* '@gnu-taler/pogen': workspace:* + '@gnu-taler/taler-util': workspace:* '@microsoft/api-extractor': ^7.13.0 '@types/node': ^14.14.22 '@typescript-eslint/eslint-plugin': ^4.14.0 @@ -243,6 +265,12 @@ packages: dev: true resolution: integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + /@babel/code-frame/7.12.13: + dependencies: + '@babel/highlight': 7.13.10 + dev: true + resolution: + integrity: sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== /@babel/core/7.12.10: dependencies: '@babel/code-frame': 7.12.11 @@ -360,6 +388,14 @@ packages: dev: true resolution: integrity: sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + /@babel/highlight/7.13.10: + dependencies: + '@babel/helper-validator-identifier': 7.12.11 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + resolution: + integrity: sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== /@babel/parser/7.12.11: dev: true engines: @@ -484,7 +520,7 @@ packages: /@nodelib/fs.scandir/2.1.4: dependencies: '@nodelib/fs.stat': 2.0.4 - run-parallel: 1.1.10 + run-parallel: 1.2.0 dev: true engines: node: '>= 8' @@ -499,7 +535,7 @@ packages: /@nodelib/fs.walk/1.2.6: dependencies: '@nodelib/fs.scandir': 2.1.4 - fastq: 1.10.0 + fastq: 1.11.0 dev: true engines: node: '>= 8' @@ -719,10 +755,10 @@ packages: /@types/node/14.14.22: resolution: integrity: sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== - /@types/node/14.14.31: + /@types/node/14.14.34: dev: true resolution: - integrity: sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== + integrity: sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA== /@types/normalize-package-data/2.4.0: dev: true resolution: @@ -746,7 +782,7 @@ packages: integrity: sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== /@types/resolve/1.17.1: dependencies: - '@types/node': 14.14.31 + '@types/node': 14.14.34 dev: true resolution: integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== @@ -928,12 +964,12 @@ packages: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 resolution: integrity: sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== - /acorn-walk/8.0.1: + /acorn-walk/8.0.2: dev: true engines: node: '>=0.4.0' resolution: - integrity: sha512-zn/7dYtoTVkG4EoMU55QlQU4F+m+T7Kren6Vj3C2DapWPnakG/DL9Ns5aPAPW5Ixd3uxXrV/BoMKKVFIazPcdg== + integrity: sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A== /acorn/7.4.1: dev: true engines: @@ -941,13 +977,13 @@ packages: hasBin: true resolution: integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - /acorn/8.0.4: + /acorn/8.1.0: dev: true engines: node: '>=0.4.0' hasBin: true resolution: - integrity: sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ== + integrity: sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA== /aggregate-error/3.1.0: dependencies: clean-stack: 2.2.0 @@ -1156,8 +1192,8 @@ packages: /ava/3.15.0: dependencies: '@concordance/react': 2.0.0 - acorn: 8.0.4 - acorn-walk: 8.0.1 + acorn: 8.1.0 + acorn-walk: 8.0.2 ansi-styles: 5.1.0 arrgv: 1.0.2 arrify: 2.0.1 @@ -1172,7 +1208,7 @@ packages: cli-truncate: 2.1.0 code-excerpt: 3.0.0 common-path-prefix: 3.0.0 - concordance: 5.0.1 + concordance: 5.0.4 convert-source-map: 1.7.0 currently-unhandled: 0.4.1 debug: 4.3.1 @@ -1187,7 +1223,7 @@ packages: is-error: 2.2.2 is-plain-object: 5.0.0 is-promise: 4.0.0 - lodash: 4.17.20 + lodash: 4.17.21 matcher: 3.0.0 md5-hex: 3.0.1 mem: 8.0.0 @@ -1208,7 +1244,7 @@ packages: supertap: 2.0.0 temp-dir: 2.0.0 trim-off-newlines: 1.0.1 - update-notifier: 5.0.1 + update-notifier: 5.1.0 write-file-atomic: 3.0.3 yargs: 16.2.0 dev: true @@ -1252,14 +1288,14 @@ packages: node: '>=8' resolution: integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - /bl/4.0.3: + /bl/4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.0 dev: true resolution: - integrity: sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== /blueimp-md5/2.18.0: dev: true resolution: @@ -1268,21 +1304,21 @@ packages: dev: true resolution: integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24= - /boxen/4.2.0: + /boxen/5.0.0: dependencies: ansi-align: 3.0.0 - camelcase: 5.3.1 - chalk: 3.0.0 + camelcase: 6.2.0 + chalk: 4.1.0 cli-boxes: 2.2.1 - string-width: 4.2.0 - term-size: 2.2.1 - type-fest: 0.8.1 + string-width: 4.2.2 + type-fest: 0.20.2 widest-line: 3.1.0 + wrap-ansi: 7.0.0 dev: true engines: - node: '>=8' + node: '>=10' resolution: - integrity: sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + integrity: sha512-5bvsqw+hhgUi3oYGK0Vf4WpIkyemp60WBInn7+WNfoISzAqk/HX4L7WNROq38E6UR/y3YADpv6pEm4BfkeEAdA== /brace-expansion/1.1.11: dependencies: balanced-match: 1.0.0 @@ -1357,6 +1393,12 @@ packages: node: '>=6' resolution: integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + /camelcase/6.2.0: + dev: true + engines: + node: '>=10' + resolution: + integrity: sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== /cancellationtoken/2.2.0: dev: false resolution: @@ -1381,15 +1423,6 @@ packages: node: '>=4' resolution: integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - /chalk/3.0.0: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - engines: - node: '>=8' - resolution: - integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== /chalk/4.1.0: dependencies: ansi-styles: 4.3.0 @@ -1428,7 +1461,7 @@ packages: dependencies: anymatch: 3.1.1 braces: 3.0.2 - glob-parent: 5.1.1 + glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.1 normalize-path: 3.0.0 @@ -1437,7 +1470,7 @@ packages: engines: node: '>= 8.10.0' optionalDependencies: - fsevents: 2.3.1 + fsevents: 2.3.2 resolution: integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== /chunkd/2.0.1: @@ -1487,7 +1520,7 @@ packages: /cli-truncate/2.1.0: dependencies: slice-ansi: 3.0.0 - string-width: 4.2.0 + string-width: 4.2.2 dev: true engines: node: '>=8' @@ -1503,7 +1536,7 @@ packages: integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== /cliui/7.0.4: dependencies: - string-width: 4.2.0 + string-width: 4.2.2 strip-ansi: 6.0.0 wrap-ansi: 7.0.0 dev: true @@ -1578,13 +1611,13 @@ packages: /concat-map/0.0.1: resolution: integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - /concordance/5.0.1: + /concordance/5.0.4: dependencies: date-time: 3.1.0 esutils: 2.0.3 fast-diff: 1.2.0 js-string-escape: 1.0.1 - lodash: 4.17.20 + lodash: 4.17.21 md5-hex: 3.0.1 semver: 7.3.4 well-known-symbols: 2.0.0 @@ -1592,11 +1625,11 @@ packages: engines: node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14' resolution: - integrity: sha512-TbNtInKVElgEBnJ1v2Xg+MFX2lvFLbmlv3EuSC5wTfCwpB8kC3w3mffF6cKuUhkn475Ym1f1I4qmuXzx2+uXpw== + integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw== /configstore/5.0.1: dependencies: dot-prop: 5.3.0 - graceful-fs: 4.2.4 + graceful-fs: 4.2.6 make-dir: 3.1.0 unique-string: 2.0.0 write-file-atomic: 3.0.3 @@ -1773,10 +1806,10 @@ packages: /del/6.0.0: dependencies: globby: 11.0.2 - graceful-fs: 4.2.4 + graceful-fs: 4.2.6 is-glob: 4.0.1 is-path-cwd: 2.2.0 - is-path-inside: 3.0.2 + is-path-inside: 3.0.3 p-map: 4.0.0 rimraf: 3.0.2 slash: 3.0.0 @@ -2003,6 +2036,12 @@ packages: dev: true resolution: integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + /esbuild/0.9.2: + dev: true + hasBin: true + requiresBuild: true + resolution: + integrity: sha512-xE3oOILjnmN8PSjkG3lT9NBbd1DbxNqolJ5qNyrLhDWsFef3yTp/KTQz1C/x7BYFKbtrr9foYtKA6KA1zuNAUQ== /escalade/3.1.1: dev: true engines: @@ -2328,7 +2367,7 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.4 '@nodelib/fs.walk': 1.2.6 - glob-parent: 5.1.1 + glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.2 picomatch: 2.2.2 @@ -2345,12 +2384,12 @@ packages: dev: true resolution: integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - /fastq/1.10.0: + /fastq/1.11.0: dependencies: reusify: 1.0.4 dev: true resolution: - integrity: sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA== + integrity: sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== /fflate/0.6.0: dev: false resolution: @@ -2486,7 +2525,7 @@ packages: - darwin resolution: integrity: sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - /fsevents/2.3.1: + /fsevents/2.3.2: dev: true engines: node: ^8.16.0 || ^10.6.0 || >=11.0.0 @@ -2494,7 +2533,7 @@ packages: os: - darwin resolution: - integrity: sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== + integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== /function-bind/1.1.1: dev: true resolution: @@ -2574,6 +2613,14 @@ packages: node: '>= 6' resolution: integrity: sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + /glob-parent/5.1.2: + dependencies: + is-glob: 4.0.1 + dev: true + engines: + node: '>= 6' + resolution: + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== /glob/7.1.6: dependencies: fs.realpath: 1.0.0 @@ -2585,14 +2632,14 @@ packages: dev: true resolution: integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - /global-dirs/2.1.0: + /global-dirs/3.0.0: dependencies: - ini: 1.3.7 + ini: 2.0.0 dev: true engines: - node: '>=8' + node: '>=10' resolution: - integrity: sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== + integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== /globals/11.12.0: dev: true engines: @@ -2642,6 +2689,10 @@ packages: dev: true resolution: integrity: sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + /graceful-fs/4.2.6: + dev: true + resolution: + integrity: sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== /handlebars/4.7.6: dependencies: minimist: 1.2.5 @@ -2814,14 +2865,16 @@ packages: dev: true resolution: integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - /ini/1.3.7: - dev: true - resolution: - integrity: sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== /ini/1.3.8: dev: true resolution: integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + /ini/2.0.0: + dev: true + engines: + node: '>=10' + resolution: + integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== /internal-slot/1.0.2: dependencies: es-abstract: 1.17.7 @@ -2919,15 +2972,15 @@ packages: node: '>=0.10.0' resolution: integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - /is-installed-globally/0.3.2: + /is-installed-globally/0.4.0: dependencies: - global-dirs: 2.1.0 - is-path-inside: 3.0.2 + global-dirs: 3.0.0 + is-path-inside: 3.0.3 dev: true engines: - node: '>=8' + node: '>=10' resolution: - integrity: sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== /is-interactive/1.0.0: dev: true engines: @@ -2974,12 +3027,12 @@ packages: node: '>=6' resolution: integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== - /is-path-inside/3.0.2: + /is-path-inside/3.0.3: dev: true engines: node: '>=8' resolution: - integrity: sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== + integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== /is-plain-object/5.0.0: dev: true engines: @@ -3274,7 +3327,7 @@ packages: integrity: sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= /load-json-file/5.3.0: dependencies: - graceful-fs: 4.2.4 + graceful-fs: 4.2.6 parse-json: 4.0.0 pify: 4.0.1 strip-bom: 3.0.0 @@ -3330,6 +3383,10 @@ packages: dev: true resolution: integrity: sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + /lodash/4.17.21: + dev: true + resolution: + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== /log-symbols/4.0.0: dependencies: chalk: 4.1.0 @@ -3532,7 +3589,7 @@ packages: /normalize-package-data/2.5.0: dependencies: hosted-git-info: 2.8.8 - resolve: 1.19.0 + resolve: 1.20.0 semver: 5.7.1 validate-npm-package-license: 3.0.4 dev: true @@ -3695,7 +3752,7 @@ packages: integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== /ora/5.3.0: dependencies: - bl: 4.0.3 + bl: 4.1.0 chalk: 4.1.0 cli-cursor: 3.1.0 cli-spinners: 2.5.0 @@ -3859,7 +3916,7 @@ packages: integrity: sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= /parse-json/5.2.0: dependencies: - '@babel/code-frame': 7.12.11 + '@babel/code-frame': 7.12.13 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.1.6 @@ -4061,6 +4118,10 @@ packages: node: '>=8' resolution: integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + /queue-microtask/1.2.2: + dev: true + resolution: + integrity: sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== /raf/3.4.1: dependencies: performance-now: 2.1.0 @@ -4359,10 +4420,12 @@ packages: dev: true resolution: integrity: sha1-gbIw6i/MYGbInjRy3nlChdmwPZE= - /run-parallel/1.1.10: + /run-parallel/1.2.0: + dependencies: + queue-microtask: 1.2.2 dev: true resolution: - integrity: sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== /safe-buffer/5.1.2: dev: true resolution: @@ -4619,6 +4682,16 @@ packages: node: '>=8' resolution: integrity: sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + /string-width/4.2.2: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.0 + dev: true + engines: + node: '>=8' + resolution: + integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== /string.prototype.matchall/4.0.3: dependencies: call-bind: 1.0.2 @@ -4753,12 +4826,6 @@ packages: node: '>=8' resolution: integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== - /term-size/2.2.1: - dev: true - engines: - node: '>=8' - resolution: - integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== /terser/5.4.0: dependencies: commander: 2.20.3 @@ -4862,6 +4929,12 @@ packages: node: '>=10' resolution: integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + /type-fest/0.20.2: + dev: true + engines: + node: '>=10' + resolution: + integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== /type-fest/0.3.1: dev: true engines: @@ -4921,6 +4994,13 @@ packages: hasBin: true resolution: integrity: sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== + /typescript/4.2.3: + dev: true + engines: + node: '>=4.2.0' + hasBin: true + resolution: + integrity: sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== /uglify-js/3.12.5: dev: true engines: @@ -4953,15 +5033,15 @@ packages: node: '>= 10.0.0' resolution: integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - /update-notifier/5.0.1: + /update-notifier/5.1.0: dependencies: - boxen: 4.2.0 + boxen: 5.0.0 chalk: 4.1.0 configstore: 5.0.1 has-yarn: 2.1.0 import-lazy: 2.1.0 is-ci: 2.0.0 - is-installed-globally: 0.3.2 + is-installed-globally: 0.4.0 is-npm: 5.0.0 is-yarn-global: 0.3.0 latest-version: 5.1.0 @@ -4973,7 +5053,7 @@ packages: engines: node: '>=10' resolution: - integrity: sha512-BuVpRdlwxeIOvmc32AGYvO1KVdPlsmqSh8KDDBxS6kDE5VR7R8OMP1d8MdhaVBvxl4H3551k9akXr0Y1iIB2Wg== + integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== /uri-js/4.4.1: dependencies: punycode: 2.1.1 @@ -5045,7 +5125,7 @@ packages: integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== /widest-line/3.1.0: dependencies: - string-width: 4.2.0 + string-width: 4.2.2 dev: true engines: node: '>=8' @@ -5074,7 +5154,7 @@ packages: /wrap-ansi/7.0.0: dependencies: ansi-styles: 4.3.0 - string-width: 4.2.0 + string-width: 4.2.2 strip-ansi: 6.0.0 dev: true engines: @@ -5127,12 +5207,12 @@ packages: node: '>=6' resolution: integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - /yargs-parser/20.2.4: + /yargs-parser/20.2.7: dev: true engines: node: '>=10' resolution: - integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + integrity: sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== /yargs/15.4.1: dependencies: cliui: 6.0.0 @@ -5157,9 +5237,9 @@ packages: escalade: 3.1.1 get-caller-file: 2.0.5 require-directory: 2.1.1 - string-width: 4.2.0 + string-width: 4.2.2 y18n: 5.0.5 - yargs-parser: 20.2.4 + yargs-parser: 20.2.7 dev: true engines: node: '>=10'