diff options
35 files changed, 2628 insertions, 2252 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index e17e44c92..565900b97 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ // Place your settings in this file to overwrite default and user settings. { // Use latest language servicesu - "typescript.tsdk": "node_modules/typescript/lib", + "typescript.tsdk": "./node_modules/typescript/lib", // Defines space handling after a comma delimiter "typescript.format.insertSpaceAfterCommaDelimiter": true, // Defines space handling after a semicolon in a for statement @@ -34,5 +34,6 @@ }, "**/*.js.map": true }, + "tslint.enable": true, "editor.wrappingIndent": "same" }
\ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..a14159944 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,44 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "option": "watch", + "problemMatcher": [ + "$tsc-watch" + ], + "group": "build", + "isBackground": true, + "promptOnClose": false + }, + { + "type": "typescript", + "tsconfig": "tsconfig.json", + "problemMatcher": [ + "$tsc" + ], + "group": "build" + }, + { + "label": "tslint", + "type": "shell", + "command": "make lint", + "problemMatcher": { + "owner": "tslint", + "applyTo": "allDocuments", + "fileLocation": "absolute", + "severity": "warning", + "pattern": "$tslint5" + }, + "group": "build" + }, + { + "label": "My Task", + "type": "shell", + "command": "echo Hello" + } + ] +}
\ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index cb385f043..f8e0c90fa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -107,7 +107,7 @@ const tsBaseArgs = { experimentalDecorators: true, module: "commonjs", sourceMap: true, - lib: ["ES6", "DOM"], + lib: ["es6", "dom"], noImplicitReturns: true, noFallthroughCasesInSwitch: true, strict: true, @@ -266,6 +266,7 @@ gulp.task("pogen", function (cb) { */ function tsconfig(confBase) { let conf = { + compileOnSave: true, compilerOptions: {}, files: [] }; diff --git a/src/amounts.ts b/src/amounts.ts new file mode 100644 index 000000000..a31bec3da --- /dev/null +++ b/src/amounts.ts @@ -0,0 +1,257 @@ +/* + This file is part of TALER + (C) 2018 GNUnet e.V. and INRIA + + 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 and helper functions for dealing with Taler amounts. + */ + +/** + * Imports. + */ +import { Checkable } from "./checkable"; + +/** + * Number of fractional units that one value unit represents. + */ +export const fractionalBase = 1e8; + +/** + * Non-negative financial amount. Fractional values are expressed as multiples + * of 1e-8. + */ +@Checkable.Class() +export class AmountJson { + /** + * Value, must be an integer. + */ + @Checkable.Number + readonly value: number; + + /** + * Fraction, must be an integer. Represent 1/1e8 of a unit. + */ + @Checkable.Number + readonly fraction: number; + + /** + * Currency of the amount. + */ + @Checkable.String + readonly currency: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => AmountJson; +} + +/** + * Result of a possibly overflowing operation. + */ +export interface Result { + /** + * Resulting, possibly saturated amount. + */ + amount: AmountJson; + /** + * Was there an over-/underflow? + */ + saturated: boolean; +} + +/** + * Get the largest amount that is safely representable. + */ +export function getMaxAmount(currency: string): AmountJson { + return { + currency, + fraction: 2 ** 32, + value: Number.MAX_SAFE_INTEGER, + }; +} + +/** + * Get an amount that represents zero units of a currency. + */ +export function getZero(currency: string): AmountJson { + return { + currency, + fraction: 0, + value: 0, + }; +} + +/** + * 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 > Number.MAX_SAFE_INTEGER) { + return { amount: getMaxAmount(currency), 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 > Number.MAX_SAFE_INTEGER) { + return { amount: getMaxAmount(currency), 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: AmountJson, b: AmountJson): number { + 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; +} + +/** + * 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; + } + return { + currency: res[1], + fraction: Math.round(fractionalBase * Number.parseFloat(res[3] || "0")), + value: Number.parseInt(res[2]), + }; +} + +/** + * Convert the amount to a float. + */ +export function toFloat(a: AmountJson): number { + return a.value + (a.fraction / fractionalBase); +} + +/** + * Convert a float to a Taler amount. + * Loss of precision possible. + */ +export function fromFloat(floatVal: number, currency: string) { + return { + currency, + fraction: Math.floor((floatVal - Math.floor(floatVal)) * fractionalBase), + value: Math.floor(floatVal), + }; +} diff --git a/src/crypto/cryptoApi-test.ts b/src/crypto/cryptoApi-test.ts index d96d69e40..88099e3eb 100644 --- a/src/crypto/cryptoApi-test.ts +++ b/src/crypto/cryptoApi-test.ts @@ -16,15 +16,15 @@ // tslint:disable:max-line-length -import {test} from "ava"; +import { test } from "ava"; import { DenominationRecord, DenominationStatus, ReserveRecord, -} from "../types"; +} from "../dbTypes"; -import {CryptoApi} from "./cryptoApi"; +import { CryptoApi } from "./cryptoApi"; const masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00"; diff --git a/src/crypto/cryptoApi.ts b/src/crypto/cryptoApi.ts index 12b1c9708..1f45ba8eb 100644 --- a/src/crypto/cryptoApi.ts +++ b/src/crypto/cryptoApi.ts @@ -23,20 +23,27 @@ /** * Imports. */ +import { AmountJson } from "../amounts"; + import { - AmountJson, CoinRecord, - CoinWithDenom, - ContractTerms, DenominationRecord, - PayCoinInfo, - PaybackRequest, PreCoinRecord, RefreshSessionRecord, ReserveRecord, TipPlanchet, WireFee, -} from "../types"; +} from "../dbTypes"; + +import { + ContractTerms, + PaybackRequest, +} from "../talerTypes"; + +import { + CoinWithDenom, + PayCoinInfo, +} from "../walletTypes"; import * as timer from "../timer"; diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts index 3b954811a..1e5f10c20 100644 --- a/src/crypto/cryptoWorker.ts +++ b/src/crypto/cryptoWorker.ts @@ -22,27 +22,33 @@ /** * Imports. */ +import * as Amounts from "../amounts"; +import { AmountJson } from "../amounts"; + import { - canonicalJson, -} from "../helpers"; -import { - AmountJson, - Amounts, - CoinPaySig, CoinRecord, CoinStatus, - CoinWithDenom, - ContractTerms, DenominationRecord, - PayCoinInfo, - PaybackRequest, PreCoinRecord, RefreshPreCoinRecord, RefreshSessionRecord, ReserveRecord, TipPlanchet, WireFee, -} from "../types"; +} from "../dbTypes"; + +import { + CoinPaySig, + ContractTerms, + PaybackRequest, +} from "../talerTypes"; + +import { + CoinWithDenom, + PayCoinInfo, +} from "../walletTypes"; + +import { canonicalJson } from "../helpers"; import { Amount, @@ -112,6 +118,9 @@ namespace RpcFunctions { } + /** + * Create a planchet used for tipping, including the private keys. + */ export function createTipPlanchet(denom: DenominationRecord): TipPlanchet { const denomPub = native.RsaPublicKey.fromCrock(denom.denomPub); const coinPriv = native.EddsaPrivateKey.create(); @@ -134,8 +143,8 @@ namespace RpcFunctions { coinPriv: coinPriv.toCrock(), coinPub: coinPub.toCrock(), coinValue: denom.value, - denomPubHash: denomPub.encode().hash().toCrock(), denomPub: denomPub.encode().toCrock(), + denomPubHash: denomPub.encode().hash().toCrock(), }; return tipPlanchet; } @@ -263,8 +272,8 @@ namespace RpcFunctions { cds: CoinWithDenom[]): PayCoinInfo { const ret: PayCoinInfo = { originalCoins: [], - updatedCoins: [], sigs: [], + updatedCoins: [], }; const contractTermsHash = hashString(canonicalJson(contractTerms)); @@ -325,8 +334,8 @@ namespace RpcFunctions { const s: CoinPaySig = { coin_pub: cd.coin.coinPub, coin_sig: coinSig, - denom_pub: cd.coin.denomPub, contribution: coinSpend.toJson(), + denom_pub: cd.coin.denomPub, ub_sig: cd.coin.denomSig, }; ret.sigs.push(s); diff --git a/src/crypto/emscInterface.ts b/src/crypto/emscInterface.ts index 8c9a34132..ce52c88bd 100644 --- a/src/crypto/emscInterface.ts +++ b/src/crypto/emscInterface.ts @@ -26,9 +26,9 @@ /** * Imports. */ -import {AmountJson} from "../types"; +import { AmountJson } from "../amounts"; -import {EmscFunGen, getLib} from "./emscLoader"; +import { EmscFunGen, getLib } from "./emscLoader"; const emscLib = getLib(); diff --git a/src/dbTypes.ts b/src/dbTypes.ts new file mode 100644 index 000000000..e0adb6fc4 --- /dev/null +++ b/src/dbTypes.ts @@ -0,0 +1,811 @@ +/* + This file is part of TALER + (C) 2018 GNUnet e.V. and INRIA + + 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 for records stored in the wallet's database. + * + * Types for the objects in the database should end in "-Record". + */ + +/** + * Imports. + */ +import { AmountJson } from "./amounts"; +import { Checkable } from "./checkable"; +import { + Auditor, + CoinPaySig, + ContractTerms, + Denomination, + PayReq, + RefundPermission, + TipResponse, +} from "./talerTypes"; + + +/** + * A reserve record as stored in the wallet's database. + */ +export interface ReserveRecord { + /** + * The reserve public key. + */ + reserve_pub: string; + + /** + * The reserve private key. + */ + reserve_priv: string; + + /** + * The exchange base URL. + */ + exchange_base_url: string; + + /** + * Time when the reserve was created. + */ + created: number; + + /** + * Time when the reserve was depleted. + * Set to 0 if not depleted yet. + */ + timestamp_depleted: number; + + /** + * Time when the reserve was confirmed. + * + * Set to 0 if not confirmed yet. + */ + timestamp_confirmed: number; + + /** + * Current amount left in the reserve + */ + current_amount: AmountJson | null; + + /** + * Amount requested when the reserve was created. + * When a reserve is re-used (rare!) the current_amount can + * be higher than the requested_amount + */ + requested_amount: AmountJson; + + /** + * What's the current amount that sits + * in precoins? + */ + precoin_amount: AmountJson; + + /** + * We got some payback to this reserve. We'll cease to automatically + * withdraw money from it. + */ + hasPayback: boolean; + + /** + * Wire information for the bank account that + * transfered funds for this reserve. + */ + senderWire?: object; +} + + +/** + * 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? + */ + exchangePub: string; + /** + * Base URL of the exchange. + */ + baseUrl: 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, + /** + * Verified as valid. + */ + VerifiedGood, + /** + * Verified as invalid. + */ + VerifiedBad, +} + + +/** + * Denomination record as stored in the wallet's database. + */ +@Checkable.Class() +export class DenominationRecord { + /** + * Value of one coin of the denomination. + */ + @Checkable.Value(AmountJson) + value: AmountJson; + + /** + * The denomination public key. + */ + @Checkable.String + denomPub: string; + + /** + * Hash of the denomination public key. + * Stored in the database for faster lookups. + */ + @Checkable.String + denomPubHash: string; + + /** + * Fee for withdrawing. + */ + @Checkable.Value(AmountJson) + feeWithdraw: AmountJson; + + /** + * Fee for depositing. + */ + @Checkable.Value(AmountJson) + feeDeposit: AmountJson; + + /** + * Fee for refreshing. + */ + @Checkable.Value(AmountJson) + feeRefresh: AmountJson; + + /** + * Fee for refunding. + */ + @Checkable.Value(AmountJson) + feeRefund: AmountJson; + + /** + * Validity start date of the denomination. + */ + @Checkable.String + stampStart: string; + + /** + * Date after which the currency can't be withdrawn anymore. + */ + @Checkable.String + stampExpireWithdraw: string; + + /** + * Date after the denomination officially doesn't exist anymore. + */ + @Checkable.String + stampExpireLegal: string; + + /** + * Data after which coins of this denomination can't be deposited anymore. + */ + @Checkable.String + stampExpireDeposit: string; + + /** + * Signature by the exchange's master key over the denomination + * information. + */ + @Checkable.String + masterSig: string; + + /** + * Did we verify the signature on the denomination? + */ + @Checkable.Number + 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. + */ + @Checkable.Boolean + isOffered: boolean; + + /** + * Base URL of the exchange. + */ + @Checkable.String + exchangeBaseUrl: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => Denomination; +} + + +/** + * Exchange record as stored in the wallet's database. + */ +export interface ExchangeRecord { + /** + * Base url of the exchange. + */ + baseUrl: string; + /** + * Master public key of the exchange. + */ + masterPublicKey: string; + /** + * Auditors (partially) auditing the exchange. + */ + auditors: Auditor[]; + + /** + * Currency that the exchange offers. + */ + currency: string; + + /** + * Timestamp for last update. + */ + lastUpdateTime: number; + + /** + * When did we actually use this exchange last (in milliseconds). If we + * never used the exchange for anything but just updated its info, this is + * set to 0. (Currently only updated when reserves are created.) + */ + lastUsedTime: number; + + /** + * Last observed protocol version. + */ + protocolVersion?: string; +} + + +/** + * A coin that isn't yet signed by an exchange. + */ +export interface PreCoinRecord { + coinPub: string; + coinPriv: string; + reservePub: string; + denomPub: string; + blindingKey: string; + withdrawSig: string; + coinEv: string; + exchangeBaseUrl: string; + coinValue: AmountJson; + /** + * Set to true if this pre-coin came from a tip. + * Until the tip is marked as "accepted", the resulting + * coin will not be used for payments. + */ + isFromTip: boolean; +} + + +/** + * Planchet for a coin during refrehs. + */ +export interface RefreshPreCoinRecord { + /** + * Public key for the coin. + */ + publicKey: string; + /** + * Private key for the coin. + */ + privateKey: string; + /** + * Blinded public key. + */ + coinEv: string; + /** + * Blinding key used. + */ + blindingKey: string; +} + + +/** + * State of returning a list of coins + * to the customer's bank account. + */ +export interface CoinsReturnRecord { + /** + * Coins that we're returning. + */ + coins: CoinPaySig[]; + + /** + * Responses to the deposit requests. + */ + responses: any; + + /** + * Ephemeral dummy merchant key for + * the coins returns operation. + */ + dummyMerchantPub: string; + + /** + * Ephemeral dummy merchant key for + * the coins returns operation. + */ + dummyMerchantPriv: string; + + /** + * Contract terms. + */ + contractTerms: string; + + /** + * Hash of contract terms. + */ + contractTermsHash: string; + + /** + * Wire info to send the money for the coins to. + */ + wire: object; + + /** + * Hash of the wire object. + */ + wireHash: string; + + /** + * All coins were deposited. + */ + finished: boolean; +} + + +/** + * Status of a coin. + */ +export enum CoinStatus { + /** + * Withdrawn and never shown to anybody. + */ + Fresh, + /** + * Currently planned to be sent to a merchant for a purchase. + */ + PurchasePending, + /** + * Used for a completed transaction and now dirty. + */ + Dirty, + /** + * A coin that was refreshed. + */ + Refreshed, + /** + * Coin marked to be paid back, but payback not finished. + */ + PaybackPending, + /** + * Coin fully paid back. + */ + PaybackDone, + /** + * Coin was dirty but can't be refreshed. + */ + Useless, + /** + * The coin was withdrawn for a tip that the user hasn't accepted yet. + */ + TainedByTip, +} + + +/** + * CoinRecord as stored in the "coins" data store + * of the wallet database. + */ +export interface CoinRecord { + /** + * 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; + + /** + * 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; + + /** + * We have withdrawn the coin, but it's not accepted by the exchange anymore. + * We have to tell an auditor and wait for compensation or for the exchange + * to fix it. + */ + suspended?: boolean; + + /** + * Blinding key used when withdrawing the coin. + * Potentionally sed again during payback. + */ + blindingKey: string; + + /** + * Reserve public key for the reserve we got this coin from, + * or zero when we got the coin from refresh. + */ + reservePub: string|undefined; + + /** + * Status of the coin. + */ + status: CoinStatus; +} + +/** + * Proposal record, stored in the wallet's database. + */ +@Checkable.Class() +export class ProposalRecord { + /** + * The contract that was offered by the merchant. + */ + @Checkable.Value(ContractTerms) + contractTerms: ContractTerms; + + /** + * Signature by the merchant over the contract details. + */ + @Checkable.String + merchantSig: string; + + /** + * Hash of the contract terms. + */ + @Checkable.String + contractTermsHash: string; + + /** + * Serial ID when the offer is stored in the wallet DB. + */ + @Checkable.Optional(Checkable.Number) + id?: number; + + /** + * Timestamp (in ms) of when the record + * was created. + */ + @Checkable.Number + timestamp: number; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => ProposalRecord; +} + + +/** + * Wire fees for an exchange. + */ +export interface ExchangeWireFeesRecord { + /** + * Base URL of the exchange. + */ + exchangeBaseUrl: string; + + /** + * Mapping from wire method type to the wire fee. + */ + feesForType: { [wireMethod: string]: WireFee[] }; +} + + +/** + * Status of a tip we got from a merchant. + */ +export interface TipRecord { + /** + * Has the user accepted the tip? Only after the tip has been accepted coins + * withdrawn from the tip may be used. + */ + accepted: boolean; + + /** + * The tipped amount. + */ + amount: AmountJson; + + /** + * Coin public keys from the planchets. + * This field is redundant and used for indexing the record via + * a multi-entry index to look up tip records by coin public key. + */ + coinPubs: string[]; + + /** + * Timestamp, the tip can't be picked up anymore after this deadline. + */ + deadline: number; + + /** + * The exchange that will sign our coins, chosen by the merchant. + */ + exchangeUrl: string; + + /** + * Domain of the merchant, necessary to uniquely identify the tip since + * merchants can freely choose the ID and a malicious merchant might cause a + * collision. + */ + merchantDomain: string; + + /** + * Planchets, the members included in TipPlanchetDetail will be sent to the + * merchant. + */ + planchets: TipPlanchet[]; + + /** + * Response if the merchant responded, + * undefined otherwise. + */ + response?: TipResponse[]; + + /** + * Identifier for the tip, chosen by the merchant. + */ + tipId: string; + + /** + * URL to go to once the tip has been accepted. + */ + nextUrl: string; + + timestamp: number; +} + + +/** + * Ongoing refresh + */ +export interface RefreshSessionRecord { + /** + * Public key that's being melted in this session. + */ + meltCoinPub: string; + + /** + * How much of the coin's value is melted away + * with this refresh session? + */ + valueWithFee: AmountJson; + + /** + * Sum of the value of denominations we want + * to withdraw in this session, without fees. + */ + valueOutput: AmountJson; + + /** + * Signature to confirm the melting. + */ + confirmSig: string; + + /** + * Hased denominations of the newly requested coins. + */ + newDenomHashes: string[]; + + /** + * Denominations of the newly requested coins. + */ + newDenoms: string[]; + + /** + * Precoins for each cut-and-choose instance. + */ + preCoinsForGammas: RefreshPreCoinRecord[][]; + + /** + * The transfer keys, kappa of them. + */ + transferPubs: string[]; + + /** + * Private keys for the transfer public keys. + */ + transferPrivs: string[]; + + /** + * The no-reveal-index after we've done the melting. + */ + norevealIndex?: number; + + /** + * Hash of the session. + */ + hash: string; + + /** + * Base URL for the exchange we're doing the refresh with. + */ + exchangeBaseUrl: string; + + /** + * Is this session finished? + */ + finished: boolean; + + /** + * Record ID when retrieved from the DB. + */ + id?: number; +} + + +/** + * Tipping planchet stored in the database. + */ +export interface TipPlanchet { + blindingKey: string; + coinEv: string; + coinPriv: string; + coinPub: string; + coinValue: AmountJson; + denomPubHash: string; + denomPub: string; +} + + +/** + * 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: number; + + /** + * End date of the fee. + */ + endStamp: number; + + /** + * Signature made by the exchange master key. + */ + sig: string; +} + +/** + * Record that stores status information about one purchase, starting from when + * the customer accepts a proposal. Includes refund status if applicable. + */ +export interface PurchaseRecord { + contractTermsHash: string; + contractTerms: ContractTerms; + payReq: PayReq; + merchantSig: string; + + /** + * The purchase isn't active anymore, it's either successfully paid or + * refunded/aborted. + */ + finished: boolean; + + refundsPending: { [refundSig: string]: RefundPermission }; + refundsDone: { [refundSig: string]: RefundPermission }; + + /** + * When was the purchase made? + * Refers to the time that the user accepted. + */ + timestamp: number; + + /** + * When was the last refund made? + * Set to 0 if no refund was made on the purchase. + */ + timestamp_refund: number; +} diff --git a/src/helpers.ts b/src/helpers.ts index 6dc080b2e..d85808acb 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -21,7 +21,8 @@ /** * Imports. */ -import {AmountJson, Amounts} from "./types"; +import { AmountJson } from "./amounts"; +import * as Amounts from "./amounts"; import URI = require("urijs"); diff --git a/src/query.ts b/src/query.ts index b199e0e9c..e45596c66 100644 --- a/src/query.ts +++ b/src/query.ts @@ -825,7 +825,7 @@ export class QueryRoot { const req = tx.objectStore(store.name).get(key); req.onsuccess = () => { results.push(req.result); - if (results.length == keys.length) { + if (results.length === keys.length) { resolve(results); } }; diff --git a/src/talerTypes.ts b/src/talerTypes.ts new file mode 100644 index 000000000..2ac2a5155 --- /dev/null +++ b/src/talerTypes.ts @@ -0,0 +1,632 @@ +/* + This file is part of TALER + (C) 2018 GNUnet e.V. and INRIA + + 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/> + */ + +/** + * Type and schema definitions for the base 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 { AmountJson } from "./amounts"; +import { Checkable } from "./checkable"; + + +/** + * Denomination as found in the /keys response from the exchange. + */ +@Checkable.Class() +export class Denomination { + /** + * Value of one coin of the denomination. + */ + @Checkable.Value(AmountJson) + value: AmountJson; + + /** + * Public signing key of the denomination. + */ + @Checkable.String + denom_pub: string; + + /** + * Fee for withdrawing. + */ + @Checkable.Value(AmountJson) + fee_withdraw: AmountJson; + + /** + * Fee for depositing. + */ + @Checkable.Value(AmountJson) + fee_deposit: AmountJson; + + /** + * Fee for refreshing. + */ + @Checkable.Value(AmountJson) + fee_refresh: AmountJson; + + /** + * Fee for refunding. + */ + @Checkable.Value(AmountJson) + fee_refund: AmountJson; + + /** + * Start date from which withdraw is allowed. + */ + @Checkable.String + stamp_start: string; + + /** + * End date for withdrawing. + */ + @Checkable.String + stamp_expire_withdraw: string; + + /** + * Expiration date after which the exchange can forget about + * the currency. + */ + @Checkable.String + stamp_expire_legal: string; + + /** + * Date after which the coins of this denomination can't be + * deposited anymore. + */ + @Checkable.String + stamp_expire_deposit: string; + + /** + * Signature over the denomination information by the exchange's master + * signing key. + */ + @Checkable.String + master_sig: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => Denomination; +} + + +/** + * Signature by the auditor that a particular denomination key is audited. + */ +@Checkable.Class() +export class AuditorDenomSig { + /** + * Denomination public key's hash. + */ + @Checkable.String + denom_pub_h: string; + + /** + * The signature. + */ + @Checkable.String + auditor_sig: string; +} + + +/** + * Auditor information as given by the exchange in /keys. + */ +@Checkable.Class() +export class Auditor { + /** + * Auditor's public key. + */ + @Checkable.String + auditor_pub: string; + + /** + * Base URL of the auditor. + */ + @Checkable.String + auditor_url: string; + + /** + * List of signatures for denominations by the auditor. + */ + @Checkable.List(Checkable.Value(AuditorDenomSig)) + denomination_keys: AuditorDenomSig[]; +} + + +/** + * Request that we send to the exchange to get a payback. + */ +export interface PaybackRequest { + /** + * Denomination public key of the coin we want to get + * paid back. + */ + denom_pub: 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; +} + + +/** + * Response that we get from the exchange for a payback request. + */ +@Checkable.Class() +export class PaybackConfirmation { + /** + * public key of the reserve that will receive the payback. + */ + @Checkable.String + reserve_pub: string; + + /** + * How much will the exchange pay back (needed by wallet in + * case coin was partially spent and wallet got restored from backup) + */ + @Checkable.Value(AmountJson) + amount: AmountJson; + + /** + * Time by which the exchange received the /payback request. + */ + @Checkable.String + timestamp: string; + + /** + * the EdDSA signature of TALER_PaybackConfirmationPS using a current + * signing key of the exchange affirming the successful + * payback request, and that the exchange promises to transfer the funds + * by the date specified (this allows the exchange delaying the transfer + * a bit to aggregate additional payback requests into a larger one). + */ + @Checkable.String + 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. + */ + @Checkable.String + exchange_pub: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => PaybackConfirmation; +} + + +/** + * Deposit permission for a single coin. + */ +export interface CoinPaySig { + /** + * 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. + */ + denom_pub: string; + /** + * The amount that is subtracted from this coin with this payment. + */ + contribution: AmountJson; +} + + +/** + * Information about an exchange as stored inside a + * merchant's contract terms. + */ +@Checkable.Class() +export class ExchangeHandle { + /** + * Master public signing key of the exchange. + */ + @Checkable.String + master_pub: string; + + /** + * Base URL of the exchange. + */ + @Checkable.String + url: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => ExchangeHandle; +} + + +/** + * Contract terms from a merchant. + */ +@Checkable.Class({validate: true}) +export class ContractTerms { + static validate(x: ContractTerms) { + if (x.exchanges.length === 0) { + throw Error("no exchanges in contract terms"); + } + } + + /** + * Hash of the merchant's wire details. + */ + @Checkable.String + H_wire: string; + + /** + * Wire method the merchant wants to use. + */ + @Checkable.String + wire_method: string; + + /** + * Human-readable short summary of the contract. + */ + @Checkable.Optional(Checkable.String) + summary?: string; + + /** + * Nonce used to ensure freshness. + */ + @Checkable.Optional(Checkable.String) + nonce?: string; + + /** + * Total amount payable. + */ + @Checkable.Value(AmountJson) + amount: AmountJson; + + /** + * Auditors accepted by the merchant. + */ + @Checkable.List(Checkable.AnyObject) + auditors: any[]; + + /** + * Deadline to pay for the contract. + */ + @Checkable.Optional(Checkable.String) + pay_deadline: string; + + /** + * Delivery locations. + */ + @Checkable.Any + locations: any; + + /** + * Maximum deposit fee covered by the merchant. + */ + @Checkable.Value(AmountJson) + max_fee: AmountJson; + + /** + * Information about the merchant. + */ + @Checkable.Any + merchant: any; + + /** + * Public key of the merchant. + */ + @Checkable.String + merchant_pub: string; + + /** + * List of accepted exchanges. + */ + @Checkable.List(Checkable.Value(ExchangeHandle)) + exchanges: ExchangeHandle[]; + + /** + * Products that are sold in this contract. + */ + @Checkable.List(Checkable.AnyObject) + products: any[]; + + /** + * Deadline for refunds. + */ + @Checkable.String + refund_deadline: string; + + /** + * Time when the contract was generated by the merchant. + */ + @Checkable.String + timestamp: string; + + /** + * Order id to uniquely identify the purchase within + * one merchant instance. + */ + @Checkable.String + order_id: string; + + /** + * URL to post the payment to. + */ + @Checkable.String + pay_url: string; + + /** + * Fulfillment URL to view the product or + * delivery status. + */ + @Checkable.String + fulfillment_url: string; + + /** + * Share of the wire fee that must be settled with one payment. + */ + @Checkable.Optional(Checkable.Number) + wire_fee_amortization?: number; + + /** + * Maximum wire fee that the merchant agrees to pay for. + */ + @Checkable.Optional(Checkable.Value(AmountJson)) + max_wire_fee?: AmountJson; + + /** + * Extra data, interpreted by the mechant only. + */ + @Checkable.Any + extra: any; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => ContractTerms; +} + + +/** + * Payment body sent to the merchant's /pay. + */ +export interface PayReq { + /** + * Coins with signature. + */ + coins: CoinPaySig[]; + + /** + * The merchant public key, used to uniquely + * identify the merchant instance. + */ + merchant_pub: string; + + /** + * Order ID that's being payed for. + */ + order_id: string; + + /** + * Exchange that the coins are from (base URL). + */ + exchange: string; +} + + +/** + * Refund permission in the format that the merchant gives it to us. + */ +export interface RefundPermission { + /** + * Amount to be refunded. + */ + refund_amount: AmountJson; + + /** + * Fee for the refund. + */ + refund_fee: AmountJson; + + /** + * Contract terms hash to identify the contract that this + * refund is for. + */ + h_contract_terms: string; + + /** + * Public key of the coin being refunded. + */ + coin_pub: string; + + /** + * Refund transaction ID between merchant and exchange. + */ + rtransaction_id: number; + + /** + * Public key of the merchant. + */ + merchant_pub: string; + + /** + * Signature made by the merchant over the refund permission. + */ + merchant_sig: string; +} + + +/** + * 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". + */ +@Checkable.Class() +export class ReserveSigSingleton { + /** + * Reserve signature. + */ + @Checkable.String + reserve_sig: string; + + /** + * Create a ReserveSigSingleton from untyped JSON. + */ + static checked: (obj: any) => ReserveSigSingleton; +} + +/** + * Response of the merchant + * to the TipPickupRequest. + */ +@Checkable.Class() +export class TipResponse { + /** + * Public key of the reserve + */ + @Checkable.String + reserve_pub: string; + + /** + * The order of the signatures matches the planchets list. + */ + @Checkable.List(Checkable.Value(ReserveSigSingleton)) + reserve_sigs: ReserveSigSingleton[]; + + /** + * Create a TipResponse from untyped JSON. + */ + static checked: (obj: any) => TipResponse; +} + +/** + * Token containing all the information for the wallet + * to process a tip. Given by the merchant to the wallet. + */ +@Checkable.Class() +export class TipToken { + /** + * Expiration for the tip. + */ + @Checkable.String + expiration: string; + + /** + * URL of the exchange that the tip can be withdrawn from. + */ + @Checkable.String + exchange_url: string; + + /** + * Merchant's URL to pick up the tip. + */ + @Checkable.String + pickup_url: string; + + /** + * Merchant-chosen tip identifier. + */ + @Checkable.String + tip_id: string; + + /** + * Amount of tip. + */ + @Checkable.Value(AmountJson) + amount: AmountJson; + + /** + * URL to navigate after finishing tip processing. + */ + @Checkable.String + next_url: string; + + /** + * Create a TipToken from untyped JSON. + * Validates the schema and throws on error. + */ + static checked: (obj: any) => TipToken; +} diff --git a/src/types-test.ts b/src/types-test.ts index 3657d6d26..097235a77 100644 --- a/src/types-test.ts +++ b/src/types-test.ts @@ -14,17 +14,17 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import {test} from "ava"; -import {Amounts} from "./types"; -import * as types from "./types"; +import { test } from "ava"; +import * as Amounts from "./amounts"; +import { ContractTerms } from "./talerTypes"; -const amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency}); +const amt = (value: number, fraction: number, currency: string): Amounts.AmountJson => ({value, fraction, currency}); test("amount addition (simple)", (t) => { const a1 = amt(1, 0, "EUR"); const a2 = amt(1, 0, "EUR"); const a3 = amt(2, 0, "EUR"); - t.true(0 === types.Amounts.cmp(Amounts.add(a1, a2).amount, a3)); + t.true(0 === Amounts.cmp(Amounts.add(a1, a2).amount, a3)); t.pass(); }); @@ -39,7 +39,7 @@ test("amount subtraction (simple)", (t) => { const a1 = amt(2, 5, "EUR"); const a2 = amt(1, 0, "EUR"); const a3 = amt(1, 5, "EUR"); - t.true(0 === types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3)); + t.true(0 === Amounts.cmp(Amounts.sub(a1, a2).amount, a3)); t.pass(); }); @@ -73,13 +73,13 @@ test("contract terms validation", (t) => { wire_method: "test", }; - types.ContractTerms.checked(c); + ContractTerms.checked(c); const c1 = JSON.parse(JSON.stringify(c)); c1.exchanges = []; try { - types.ContractTerms.checked(c1); + ContractTerms.checked(c1); } catch (e) { t.pass(); return; diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index ae4454a83..000000000 --- a/src/types.ts +++ /dev/null @@ -1,2048 +0,0 @@ -/* - This file is part of TALER - (C) 2015-2017 GNUnet e.V. and INRIA - - 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/> - */ - -/** - * Common types that are used by Taler and some helper functions - * to deal with them. - * - * Note most types are defined in wallet.ts, types that - * are defined in types.ts are intended to be used by components - * that do not depend on the whole wallet implementation. - */ - -/** - * Imports. - */ -import { Checkable } from "./checkable"; -import * as LibtoolVersion from "./libtoolVersion"; - -/** - * Non-negative financial amount. Fractional values are expressed as multiples - * of 1e-8. - */ -@Checkable.Class() -export class AmountJson { - /** - * Value, must be an integer. - */ - @Checkable.Number - readonly value: number; - - /** - * Fraction, must be an integer. Represent 1/1e8 of a unit. - */ - @Checkable.Number - readonly fraction: number; - - /** - * Currency of the amount. - */ - @Checkable.String - readonly currency: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => AmountJson; -} - - -/** - * Amount with a sign. - */ -export interface SignedAmountJson { - /** - * The absolute amount. - */ - amount: AmountJson; - /** - * Sign. - */ - isNegative: boolean; -} - - -/** - * A reserve record as stored in the wallet's database. - */ -export interface ReserveRecord { - /** - * The reserve public key. - */ - reserve_pub: string; - - /** - * The reserve private key. - */ - reserve_priv: string; - - /** - * The exchange base URL. - */ - exchange_base_url: string; - - /** - * Time when the reserve was created. - */ - created: number; - - /** - * Time when the reserve was depleted. - * Set to 0 if not depleted yet. - */ - timestamp_depleted: number; - - /** - * Time when the reserve was confirmed. - * - * Set to 0 if not confirmed yet. - */ - timestamp_confirmed: number; - - /** - * Current amount left in the reserve - */ - current_amount: AmountJson | null; - - /** - * Amount requested when the reserve was created. - * When a reserve is re-used (rare!) the current_amount can - * be higher than the requested_amount - */ - requested_amount: AmountJson; - - /** - * What's the current amount that sits - * in precoins? - */ - precoin_amount: AmountJson; - - /** - * We got some payback to this reserve. We'll cease to automatically - * withdraw money from it. - */ - hasPayback: boolean; - - /** - * Wire information for the bank account that - * transfered funds for this reserve. - */ - senderWire?: object; -} - - -/** - * 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? - */ - exchangePub: string; - /** - * Base URL of the exchange. - */ - baseUrl: 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[]; -} - - -/** - * Response for the create reserve request to the wallet. - */ -@Checkable.Class() -export class CreateReserveResponse { - /** - * Exchange URL where the bank should create the reserve. - * The URL is canonicalized in the response. - */ - @Checkable.String - exchange: string; - - /** - * Reserve public key of the newly created reserve. - */ - @Checkable.String - reservePub: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => CreateReserveResponse; -} - - -/** - * Status of a denomination. - */ -export enum DenominationStatus { - /** - * Verification was delayed. - */ - Unverified, - /** - * Verified as valid. - */ - VerifiedGood, - /** - * Verified as invalid. - */ - VerifiedBad, -} - -/** - * Denomination record as stored in the wallet's database. - */ -@Checkable.Class() -export class DenominationRecord { - /** - * Value of one coin of the denomination. - */ - @Checkable.Value(AmountJson) - value: AmountJson; - - /** - * The denomination public key. - */ - @Checkable.String - denomPub: string; - - /** - * Hash of the denomination public key. - * Stored in the database for faster lookups. - */ - @Checkable.String - denomPubHash: string; - - /** - * Fee for withdrawing. - */ - @Checkable.Value(AmountJson) - feeWithdraw: AmountJson; - - /** - * Fee for depositing. - */ - @Checkable.Value(AmountJson) - feeDeposit: AmountJson; - - /** - * Fee for refreshing. - */ - @Checkable.Value(AmountJson) - feeRefresh: AmountJson; - - /** - * Fee for refunding. - */ - @Checkable.Value(AmountJson) - feeRefund: AmountJson; - - /** - * Validity start date of the denomination. - */ - @Checkable.String - stampStart: string; - - /** - * Date after which the currency can't be withdrawn anymore. - */ - @Checkable.String - stampExpireWithdraw: string; - - /** - * Date after the denomination officially doesn't exist anymore. - */ - @Checkable.String - stampExpireLegal: string; - - /** - * Data after which coins of this denomination can't be deposited anymore. - */ - @Checkable.String - stampExpireDeposit: string; - - /** - * Signature by the exchange's master key over the denomination - * information. - */ - @Checkable.String - masterSig: string; - - /** - * Did we verify the signature on the denomination? - */ - @Checkable.Number - 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. - */ - @Checkable.Boolean - isOffered: boolean; - - /** - * Base URL of the exchange. - */ - @Checkable.String - exchangeBaseUrl: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => Denomination; -} - -/** - * Denomination as found in the /keys response from the exchange. - */ -@Checkable.Class() -export class Denomination { - /** - * Value of one coin of the denomination. - */ - @Checkable.Value(AmountJson) - value: AmountJson; - - /** - * Public signing key of the denomination. - */ - @Checkable.String - denom_pub: string; - - /** - * Fee for withdrawing. - */ - @Checkable.Value(AmountJson) - fee_withdraw: AmountJson; - - /** - * Fee for depositing. - */ - @Checkable.Value(AmountJson) - fee_deposit: AmountJson; - - /** - * Fee for refreshing. - */ - @Checkable.Value(AmountJson) - fee_refresh: AmountJson; - - /** - * Fee for refunding. - */ - @Checkable.Value(AmountJson) - fee_refund: AmountJson; - - /** - * Start date from which withdraw is allowed. - */ - @Checkable.String - stamp_start: string; - - /** - * End date for withdrawing. - */ - @Checkable.String - stamp_expire_withdraw: string; - - /** - * Expiration date after which the exchange can forget about - * the currency. - */ - @Checkable.String - stamp_expire_legal: string; - - /** - * Date after which the coins of this denomination can't be - * deposited anymore. - */ - @Checkable.String - stamp_expire_deposit: string; - - /** - * Signature over the denomination information by the exchange's master - * signing key. - */ - @Checkable.String - master_sig: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => Denomination; -} - - -/** - * Signature by the auditor that a particular denomination key is audited. - */ -@Checkable.Class() -export class AuditorDenomSig { - /** - * Denomination public key's hash. - */ - @Checkable.String - denom_pub_h: string; - - /** - * The signature. - */ - @Checkable.String - auditor_sig: string; -} - -/** - * Auditor information as given by the exchange in /keys. - */ -@Checkable.Class() -export class Auditor { - /** - * Auditor's public key. - */ - @Checkable.String - auditor_pub: string; - - /** - * Base URL of the auditor. - */ - @Checkable.String - auditor_url: string; - - /** - * List of signatures for denominations by the auditor. - */ - @Checkable.List(Checkable.Value(AuditorDenomSig)) - denomination_keys: AuditorDenomSig[]; -} - - -/** - * Exchange record as stored in the wallet's database. - */ -export interface ExchangeRecord { - /** - * Base url of the exchange. - */ - baseUrl: string; - /** - * Master public key of the exchange. - */ - masterPublicKey: string; - /** - * Auditors (partially) auditing the exchange. - */ - auditors: Auditor[]; - - /** - * Currency that the exchange offers. - */ - currency: string; - - /** - * Timestamp for last update. - */ - lastUpdateTime: number; - - /** - * When did we actually use this exchange last (in milliseconds). If we - * never used the exchange for anything but just updated its info, this is - * set to 0. (Currently only updated when reserves are created.) - */ - lastUsedTime: number; - - /** - * Last observed protocol version. - */ - protocolVersion?: string; -} - -/** - * Wire info, sent to the bank when creating a reserve. Fee information will - * be filtered out. Only methods that the bank also supports should be sent. - */ -export interface WireInfo { - /** - * Mapping from wire method type to the exchange's wire info, - * excluding fees. - */ - [type: string]: any; -} - - -/** - * Information about what will happen when creating a reserve. - * - * Sent to the wallet frontend to be rendered and shown to the user. - */ -export interface ReserveCreationInfo { - /** - * Exchange that the reserve will be created at. - */ - exchangeInfo: ExchangeRecord; - /** - * Filtered wire info to send to the bank. - */ - wireInfo: WireInfo; - /** - * Selected denominations for withdraw. - */ - selectedDenoms: DenominationRecord[]; - /** - * Fees for withdraw. - */ - withdrawFee: AmountJson; - /** - * Remaining balance that is too small to be withdrawn. - */ - overhead: AmountJson; - /** - * Wire fees from the exchange. - */ - wireFees: ExchangeWireFeesRecord; - /** - * Does the wallet know about an auditor for - * the exchange that the reserve. - */ - isAudited: boolean; - /** - * The exchange is trusted directly. - */ - isTrusted: boolean; - /** - * The earliest deposit expiration of the selected coins. - */ - earliestDepositExpiration: number; - /** - * 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; -} - - -/** - * A coin that isn't yet signed by an exchange. - */ -export interface PreCoinRecord { - coinPub: string; - coinPriv: string; - reservePub: string; - denomPub: string; - blindingKey: string; - withdrawSig: string; - coinEv: string; - exchangeBaseUrl: string; - coinValue: AmountJson; - /** - * Set to true if this pre-coin came from a tip. - * Until the tip is marked as "accepted", the resulting - * coin will not be used for payments. - */ - isFromTip: boolean; -} - -/** - * Planchet for a coin during refrehs. - */ -export interface RefreshPreCoinRecord { - /** - * Public key for the coin. - */ - publicKey: string; - /** - * Private key for the coin. - */ - privateKey: string; - /** - * Blinded public key. - */ - coinEv: string; - /** - * Blinding key used. - */ - blindingKey: string; -} - -/** - * Request that we send to the exchange to get a payback. - */ -export interface PaybackRequest { - /** - * Denomination public key of the coin we want to get - * paid back. - */ - denom_pub: 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; -} - -/** - * Response that we get from the exchange for a payback request. - */ -@Checkable.Class() -export class PaybackConfirmation { - /** - * public key of the reserve that will receive the payback. - */ - @Checkable.String - reserve_pub: string; - - /** - * How much will the exchange pay back (needed by wallet in - * case coin was partially spent and wallet got restored from backup) - */ - @Checkable.Value(AmountJson) - amount: AmountJson; - - /** - * Time by which the exchange received the /payback request. - */ - @Checkable.String - timestamp: string; - - /** - * the EdDSA signature of TALER_PaybackConfirmationPS using a current - * signing key of the exchange affirming the successful - * payback request, and that the exchange promises to transfer the funds - * by the date specified (this allows the exchange delaying the transfer - * a bit to aggregate additional payback requests into a larger one). - */ - @Checkable.String - 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. - */ - @Checkable.String - exchange_pub: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => PaybackConfirmation; -} - -/** - * Ongoing refresh - */ -export interface RefreshSessionRecord { - /** - * Public key that's being melted in this session. - */ - meltCoinPub: string; - - /** - * How much of the coin's value is melted away - * with this refresh session? - */ - valueWithFee: AmountJson; - - /** - * Sum of the value of denominations we want - * to withdraw in this session, without fees. - */ - valueOutput: AmountJson; - - /** - * Signature to confirm the melting. - */ - confirmSig: string; - - /** - * Hased denominations of the newly requested coins. - */ - newDenomHashes: string[]; - - /** - * Denominations of the newly requested coins. - */ - newDenoms: string[]; - - /** - * Precoins for each cut-and-choose instance. - */ - preCoinsForGammas: RefreshPreCoinRecord[][]; - - /** - * The transfer keys, kappa of them. - */ - transferPubs: string[]; - - /** - * Private keys for the transfer public keys. - */ - transferPrivs: string[]; - - /** - * The no-reveal-index after we've done the melting. - */ - norevealIndex?: number; - - /** - * Hash of the session. - */ - hash: string; - - /** - * Base URL for the exchange we're doing the refresh with. - */ - exchangeBaseUrl: string; - - /** - * Is this session finished? - */ - finished: boolean; - - /** - * Record ID when retrieved from the DB. - */ - id?: number; -} - - -/** - * Deposit permission for a single coin. - */ -export interface CoinPaySig { - /** - * 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. - */ - denom_pub: string; - /** - * The amount that is subtracted from this coin with this payment. - */ - contribution: AmountJson; -} - - -/** - * Status of a coin. - */ -export enum CoinStatus { - /** - * Withdrawn and never shown to anybody. - */ - Fresh, - /** - * Currently planned to be sent to a merchant for a purchase. - */ - PurchasePending, - /** - * Used for a completed transaction and now dirty. - */ - Dirty, - /** - * A coin that was refreshed. - */ - Refreshed, - /** - * Coin marked to be paid back, but payback not finished. - */ - PaybackPending, - /** - * Coin fully paid back. - */ - PaybackDone, - /** - * Coin was dirty but can't be refreshed. - */ - Useless, - /** - * The coin was withdrawn for a tip that the user hasn't accepted yet. - */ - TainedByTip, -} - - -/** - * State of returning a list of coins - * to the customer's bank account. - */ -export interface CoinsReturnRecord { - /** - * Coins that we're returning. - */ - coins: CoinPaySig[]; - - /** - * Responses to the deposit requests. - */ - responses: any; - - /** - * Ephemeral dummy merchant key for - * the coins returns operation. - */ - dummyMerchantPub: string; - - /** - * Ephemeral dummy merchant key for - * the coins returns operation. - */ - dummyMerchantPriv: string; - - /** - * Contract terms. - */ - contractTerms: string; - - /** - * Hash of contract terms. - */ - contractTermsHash: string; - - /** - * Wire info to send the money for the coins to. - */ - wire: object; - - /** - * Hash of the wire object. - */ - wireHash: string; - - /** - * All coins were deposited. - */ - finished: boolean; -} - - -/** - * CoinRecord as stored in the "coins" data store - * of the wallet database. - */ -export interface CoinRecord { - /** - * 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; - - /** - * 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; - - /** - * We have withdrawn the coin, but it's not accepted by the exchange anymore. - * We have to tell an auditor and wait for compensation or for the exchange - * to fix it. - */ - suspended?: boolean; - - /** - * Blinding key used when withdrawing the coin. - * Potentionally sed again during payback. - */ - blindingKey: string; - - /** - * Reserve public key for the reserve we got this coin from, - * or zero when we got the coin from refresh. - */ - reservePub: string|undefined; - - /** - * Status of the coin. - */ - status: CoinStatus; -} - - -/** - * Information about an exchange as stored inside a - * merchant's contract terms. - */ -@Checkable.Class() -export class ExchangeHandle { - /** - * Master public signing key of the exchange. - */ - @Checkable.String - master_pub: string; - - /** - * Base URL of the exchange. - */ - @Checkable.String - url: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => ExchangeHandle; -} - - -/** - * Mapping from currency/exchange to detailed balance - * information. - */ -export interface WalletBalance { - /** - * Mapping from currency name to detailed balance info. - */ - byExchange: { [exchangeBaseUrl: string]: WalletBalanceEntry }; - - /** - * Mapping from currency name to detailed balance info. - */ - byCurrency: { [currency: string]: WalletBalanceEntry }; -} - - -/** - * Detailed wallet balance for a particular currency. - */ -export interface WalletBalanceEntry { - /** - * Directly available amount. - */ - available: AmountJson; - /** - * Amount that we're waiting for (refresh, withdrawal). - */ - pendingIncoming: AmountJson; - /** - * Amount that's marked for a pending payment. - */ - pendingPayment: AmountJson; - /** - * Amount that was paid back and we could withdraw again. - */ - paybackAmount: AmountJson; -} - - -/** - * Contract terms from a merchant. - */ -@Checkable.Class({validate: true}) -export class ContractTerms { - static validate(x: ContractTerms) { - if (x.exchanges.length === 0) { - throw Error("no exchanges in contract terms"); - } - } - - /** - * Hash of the merchant's wire details. - */ - @Checkable.String - H_wire: string; - - /** - * Wire method the merchant wants to use. - */ - @Checkable.String - wire_method: string; - - /** - * Human-readable short summary of the contract. - */ - @Checkable.Optional(Checkable.String) - summary?: string; - - /** - * Nonce used to ensure freshness. - */ - @Checkable.Optional(Checkable.String) - nonce?: string; - - /** - * Total amount payable. - */ - @Checkable.Value(AmountJson) - amount: AmountJson; - - /** - * Auditors accepted by the merchant. - */ - @Checkable.List(Checkable.AnyObject) - auditors: any[]; - - /** - * Deadline to pay for the contract. - */ - @Checkable.Optional(Checkable.String) - pay_deadline: string; - - /** - * Delivery locations. - */ - @Checkable.Any - locations: any; - - /** - * Maximum deposit fee covered by the merchant. - */ - @Checkable.Value(AmountJson) - max_fee: AmountJson; - - /** - * Information about the merchant. - */ - @Checkable.Any - merchant: any; - - /** - * Public key of the merchant. - */ - @Checkable.String - merchant_pub: string; - - /** - * List of accepted exchanges. - */ - @Checkable.List(Checkable.Value(ExchangeHandle)) - exchanges: ExchangeHandle[]; - - /** - * Products that are sold in this contract. - */ - @Checkable.List(Checkable.AnyObject) - products: any[]; - - /** - * Deadline for refunds. - */ - @Checkable.String - refund_deadline: string; - - /** - * Time when the contract was generated by the merchant. - */ - @Checkable.String - timestamp: string; - - /** - * Order id to uniquely identify the purchase within - * one merchant instance. - */ - @Checkable.String - order_id: string; - - /** - * URL to post the payment to. - */ - @Checkable.String - pay_url: string; - - /** - * Fulfillment URL to view the product or - * delivery status. - */ - @Checkable.String - fulfillment_url: string; - - /** - * Share of the wire fee that must be settled with one payment. - */ - @Checkable.Optional(Checkable.Number) - wire_fee_amortization?: number; - - /** - * Maximum wire fee that the merchant agrees to pay for. - */ - @Checkable.Optional(Checkable.Value(AmountJson)) - max_wire_fee?: AmountJson; - - /** - * Extra data, interpreted by the mechant only. - */ - @Checkable.Any - extra: any; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => ContractTerms; -} - - -/** - * Proposal record, stored in the wallet's database. - */ -@Checkable.Class() -export class ProposalRecord { - /** - * The contract that was offered by the merchant. - */ - @Checkable.Value(ContractTerms) - contractTerms: ContractTerms; - - /** - * Signature by the merchant over the contract details. - */ - @Checkable.String - merchantSig: string; - - /** - * Hash of the contract terms. - */ - @Checkable.String - contractTermsHash: string; - - /** - * Serial ID when the offer is stored in the wallet DB. - */ - @Checkable.Optional(Checkable.Number) - id?: number; - - /** - * Timestamp (in ms) of when the record - * was created. - */ - @Checkable.Number - timestamp: number; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => ProposalRecord; -} - - -/** - * 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: number; - - /** - * End date of the fee. - */ - endStamp: number; - - /** - * Signature made by the exchange master key. - */ - sig: string; -} - - -/** - * Wire fees for an exchange. - */ -export interface ExchangeWireFeesRecord { - /** - * Base URL of the exchange. - */ - exchangeBaseUrl: string; - - /** - * Mapping from wire method type to the wire fee. - */ - feesForType: { [wireMethod: string]: WireFee[] }; -} - - -/** - * Coins used for a payment, with signatures authorizing the payment and the - * coins with remaining value updated to accomodate for a payment. - */ -export interface PayCoinInfo { - originalCoins: CoinRecord[]; - updatedCoins: CoinRecord[]; - sigs: CoinPaySig[]; -} - - -/** - * Amount helpers. - */ -export namespace Amounts { - /** - * Number of fractional units that one value unit represents. - */ - export const fractionalBase = 1e8; - - /** - * Result of a possibly overflowing operation. - */ - export interface Result { - /** - * Resulting, possibly saturated amount. - */ - amount: AmountJson; - /** - * Was there an over-/underflow? - */ - saturated: boolean; - } - - /** - * Get the largest amount that is safely representable. - */ - export function getMaxAmount(currency: string): AmountJson { - return { - currency, - fraction: 2 ** 32, - value: Number.MAX_SAFE_INTEGER, - }; - } - - /** - * Get an amount that represents zero units of a currency. - */ - export function getZero(currency: string): AmountJson { - return { - currency, - fraction: 0, - value: 0, - }; - } - - /** - * 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 > Number.MAX_SAFE_INTEGER) { - return { amount: getMaxAmount(currency), 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 > Number.MAX_SAFE_INTEGER) { - return { amount: getMaxAmount(currency), 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: AmountJson, b: AmountJson): number { - 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; - } - - /** - * 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; - } - return { - currency: res[1], - fraction: Math.round(fractionalBase * Number.parseFloat(res[3] || "0")), - value: Number.parseInt(res[2]), - }; - } - - /** - * Convert the amount to a float. - */ - export function toFloat(a: AmountJson): number { - return a.value + (a.fraction / fractionalBase); - } - - /** - * Convert a float to a Taler amount. - * Loss of precision possible. - */ - export function fromFloat(floatVal: number, currency: string) { - return { - currency, - fraction: Math.floor((floatVal - Math.floor(floatVal)) * fractionalBase), - value: Math.floor(floatVal), - }; - } -} - - -/** - * Listener for notifications from the wallet. - */ -export interface Notifier { - /** - * Called when a new notification arrives. - */ - notify(): void; -} - -/** - * For terseness. - */ -export function mkAmount(value: number, fraction: number, currency: string): AmountJson { - return {value, fraction, currency}; -} - -/** - * Possible results for checkPay. - */ -export interface CheckPayResult { - status: "paid" | "payment-possible" | "insufficient-balance"; - coinSelection?: CoinSelectionResult; -} - -/** - * Possible results for confirmPay. - */ -export type ConfirmPayResult = "paid" | "insufficient-balance"; - - -/** - * Activity history record. - */ -export interface HistoryRecord { - /** - * Type of the history event. - */ - type: string; - - /** - * Time when the activity was recorded. - */ - timestamp: number; - - /** - * Subject of the entry. Used to group multiple history records together. - * Only the latest history record with the same subjectId will be shown. - */ - subjectId?: string; - - /** - * Details used when rendering the history record. - */ - detail: any; -} - - -/** - * Payment body sent to the merchant's /pay. - */ -export interface PayReq { - /** - * Coins with signature. - */ - coins: CoinPaySig[]; - - /** - * The merchant public key, used to uniquely - * identify the merchant instance. - */ - merchant_pub: string; - - /** - * Order ID that's being payed for. - */ - order_id: string; - - /** - * Exchange that the coins are from (base URL). - */ - exchange: string; -} - - -/** - * Response to a query payment request. Tagged union over the 'found' field. - */ -export type QueryPaymentResult = QueryPaymentNotFound | QueryPaymentFound; - -/** - * Query payment response when the payment was found. - */ -export interface QueryPaymentNotFound { - found: false; -} - -/** - * Query payment response when the payment wasn't found. - */ -export interface QueryPaymentFound { - found: true; - contractTermsHash: string; - contractTerms: ContractTerms; - payReq: PayReq; -} - -/** - * 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 types stored in the wallet. - */ - senderWires: object[]; -} - - -/** - * Request to mark a reserve as confirmed. - */ -@Checkable.Class() -export class CreateReserveRequest { - /** - * The initial amount for the reserve. - */ - @Checkable.Value(AmountJson) - amount: AmountJson; - - /** - * Exchange URL where the bank should create the reserve. - */ - @Checkable.String - exchange: string; - - /** - * Wire details for the bank account that sent the funds to the exchange. - */ - @Checkable.Optional(Checkable.Any) - senderWire?: object; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => CreateReserveRequest; -} - - -/** - * Request to mark a reserve as confirmed. - */ -@Checkable.Class() -export class ConfirmReserveRequest { - /** - * Public key of then reserve that should be marked - * as confirmed. - */ - @Checkable.String - reservePub: string; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => ConfirmReserveRequest; -} - - -/** - * Wire coins to the user's own bank account. - */ -@Checkable.Class() -export class ReturnCoinsRequest { - /** - * The amount to wire. - */ - @Checkable.Value(AmountJson) - amount: AmountJson; - - /** - * The exchange to take the coins from. - */ - @Checkable.String - exchange: string; - - /** - * Wire details for the bank account of the customer that will - * receive the funds. - */ - @Checkable.Any - senderWire?: object; - - /** - * Verify that a value matches the schema of this class and convert it into a - * member. - */ - static checked: (obj: any) => ReturnCoinsRequest; -} - - -/** - * Refund permission in the format that the merchant gives it to us. - */ -export interface RefundPermission { - /** - * Amount to be refunded. - */ - refund_amount: AmountJson; - - /** - * Fee for the refund. - */ - refund_fee: AmountJson; - - /** - * Contract terms hash to identify the contract that this - * refund is for. - */ - h_contract_terms: string; - - /** - * Public key of the coin being refunded. - */ - coin_pub: string; - - /** - * Refund transaction ID between merchant and exchange. - */ - rtransaction_id: number; - - /** - * Public key of the merchant. - */ - merchant_pub: string; - - /** - * Signature made by the merchant over the refund permission. - */ - merchant_sig: string; -} - - -/** - * Record that stores status information about one purchase, starting from when - * the customer accepts a proposal. Includes refund status if applicable. - */ -export interface PurchaseRecord { - contractTermsHash: string; - contractTerms: ContractTerms; - payReq: PayReq; - merchantSig: string; - - /** - * The purchase isn't active anymore, it's either successfully paid or - * refunded/aborted. - */ - finished: boolean; - - refundsPending: { [refundSig: string]: RefundPermission }; - refundsDone: { [refundSig: string]: RefundPermission }; - - /** - * When was the purchase made? - * Refers to the time that the user accepted. - */ - timestamp: number; - - /** - * When was the last refund made? - * Set to 0 if no refund was made on the purchase. - */ - timestamp_refund: number; -} - - -/** - * Result of selecting coins, contains the exchange, and selected - * coins with their denomination. - */ -export interface CoinSelectionResult { - exchangeUrl: string; - cds: CoinWithDenom[]; - totalFees: AmountJson; -} - - -/** - * Named tuple of coin and denomination. - */ -export interface CoinWithDenom { - /** - * A coin. Must have the same denomination public key as the associated - * denomination. - */ - coin: CoinRecord; - /** - * An associated denomination. - */ - denom: DenominationRecord; -} - - -/** - * 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; -} - - -export interface TipPickupRequest { - /** - * Identifier of the tip. - */ - tip_id: string; - - /** - * List of planchets the wallet wants to use for the tip. - */ - planchets: TipPlanchetDetail[]; -} - -@Checkable.Class() -export class ReserveSigSingleton { - @Checkable.String - reserve_sig: string; - - static checked: (obj: any) => ReserveSigSingleton; -} - -/** - * Response of the merchant - * to the TipPickupRequest. - */ -@Checkable.Class() -export class TipResponse { - /** - * Public key of the reserve - */ - @Checkable.String - reserve_pub: string; - - /** - * The order of the signatures matches the planchets list. - */ - @Checkable.List(Checkable.Value(ReserveSigSingleton)) - reserve_sigs: ReserveSigSingleton[]; - - static checked: (obj: any) => TipResponse; -} - - -/** - * Tipping planchet stored in the database. - */ -export interface TipPlanchet { - blindingKey: string; - coinEv: string; - coinPriv: string; - coinPub: string; - coinValue: AmountJson; - denomPubHash: string; - denomPub: string; -} - -/** - * Status of a tip we got from a merchant. - */ -export interface TipRecord { - /** - * Has the user accepted the tip? Only after the tip has been accepted coins - * withdrawn from the tip may be used. - */ - accepted: boolean; - - /** - * The tipped amount. - */ - amount: AmountJson; - - /** - * Coin public keys from the planchets. - * This field is redundant and used for indexing the record via - * a multi-entry index to look up tip records by coin public key. - */ - coinPubs: string[]; - - /** - * Timestamp, the tip can't be picked up anymore after this deadline. - */ - deadline: number; - - /** - * The exchange that will sign our coins, chosen by the merchant. - */ - exchangeUrl: string; - - /** - * Domain of the merchant, necessary to uniquely identify the tip since - * merchants can freely choose the ID and a malicious merchant might cause a - * collision. - */ - merchantDomain: string; - - /** - * Planchets, the members included in TipPlanchetDetail will be sent to the - * merchant. - */ - planchets: TipPlanchet[]; - - /** - * Response if the merchant responded, - * undefined otherwise. - */ - response?: TipResponse[]; - - /** - * Identifier for the tip, chosen by the merchant. - */ - tipId: string; - - /** - * URL to go to once the tip has been accepted. - */ - nextUrl: string; - - timestamp: number; -} - - -export interface TipStatus { - tip: TipRecord; - rci?: ReserveCreationInfo; -} - - -@Checkable.Class() -export class TipStatusRequest { - @Checkable.String - tipId: string; - - @Checkable.String - merchantDomain: string; - - static checked: (obj: any) => TipStatusRequest; -} - - -@Checkable.Class() -export class AcceptTipRequest { - @Checkable.String - tipId: string; - - @Checkable.String - merchantDomain: string; - - static checked: (obj: any) => AcceptTipRequest; -} - - -@Checkable.Class() -export class ProcessTipResponseRequest { - @Checkable.String - tipId: string; - - @Checkable.String - merchantDomain: string; - - @Checkable.Value(TipResponse) - tipResponse: TipResponse; - - static checked: (obj: any) => ProcessTipResponseRequest; -} - -@Checkable.Class() -export class GetTipPlanchetsRequest { - @Checkable.String - tipId: string; - - @Checkable.String - merchantDomain: string; - - @Checkable.Optional(Checkable.Value(AmountJson)) - amount: AmountJson; - - @Checkable.Number - deadline: number; - - @Checkable.String - exchangeUrl: string; - - @Checkable.String - nextUrl: string; - - static checked: (obj: any) => GetTipPlanchetsRequest; -} - -@Checkable.Class() -export class TipToken { - @Checkable.String - expiration: string; - - @Checkable.String - exchange_url: string; - - @Checkable.String - pickup_url: string; - - @Checkable.String - tip_id: string; - - @Checkable.Value(AmountJson) - amount: AmountJson; - - @Checkable.String - next_url: string; - - static checked: (obj: any) => TipToken; -} diff --git a/src/wallet-test.ts b/src/wallet-test.ts index 037cc7592..6b06085c0 100644 --- a/src/wallet-test.ts +++ b/src/wallet-test.ts @@ -15,13 +15,19 @@ */ -import {test} from "ava"; -import * as types from "./types"; +import { test } from "ava"; + +import * as dbTypes from "./dbTypes"; +import * as types from "./walletTypes"; + import * as wallet from "./wallet"; +import { AmountJson} from "./amounts"; +import * as Amounts from "./amounts"; + -function a(x: string): types.AmountJson { - const amt = types.Amounts.parse(x); +function a(x: string): AmountJson { + const amt = Amounts.parse(x); if (!amt) { throw Error("invalid amount"); } @@ -40,7 +46,7 @@ function fakeCwd(current: string, value: string, feeDeposit: string): types.Coin denomSig: "(mock)", exchangeBaseUrl: "(mock)", reservePub: "(mock)", - status: types.CoinStatus.Fresh, + status: dbTypes.CoinStatus.Fresh, }, denom: { denomPub: "(mock)", @@ -56,7 +62,7 @@ function fakeCwd(current: string, value: string, feeDeposit: string): types.Coin stampExpireLegal: "(mock)", stampExpireWithdraw: "(mock)", stampStart: "(mock)", - status: types.DenominationStatus.VerifiedGood, + status: dbTypes.DenominationStatus.VerifiedGood, value: a(value), }, }; diff --git a/src/wallet.ts b/src/wallet.ts index ca94e1d59..41f8e7276 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -43,56 +43,64 @@ import { QueryRoot, Store, } from "./query"; -import {TimerGroup} from "./timer"; +import { TimerGroup } from "./timer"; + +import { AmountJson } from "./amounts"; +import * as Amounts from "./amounts"; + import { - AmountJson, - Amounts, - Auditor, - CheckPayResult, - CoinPaySig, CoinRecord, - CoinSelectionResult, CoinStatus, - CoinWithDenom, - ConfirmPayResult, - ConfirmReserveRequest, - ContractTerms, - CreateReserveRequest, - CreateReserveResponse, CurrencyRecord, - Denomination, DenominationRecord, DenominationStatus, - ExchangeHandle, ExchangeRecord, ExchangeWireFeesRecord, - HistoryRecord, - Notifier, - PayCoinInfo, - PayReq, - PaybackConfirmation, PreCoinRecord, ProposalRecord, PurchaseRecord, - QueryPaymentResult, RefreshPreCoinRecord, RefreshSessionRecord, + ReserveRecord, + TipRecord, + WireFee, +} from "./dbTypes"; + +import URI = require("urijs"); + +import { + Auditor, + CoinPaySig, + ContractTerms, + Denomination, + ExchangeHandle, + PayReq, + PaybackConfirmation, RefundPermission, + TipPlanchetDetail, + TipResponse, +} from "./talerTypes"; +import { + CheckPayResult, + CoinSelectionResult, + CoinWithDenom, + ConfirmPayResult, + ConfirmReserveRequest, + CreateReserveRequest, + CreateReserveResponse, + HistoryRecord, + Notifier, + PayCoinInfo, + QueryPaymentResult, ReserveCreationInfo, - ReserveRecord, ReturnCoinsRequest, SenderWireInfos, - TipPlanchetDetail, - TipRecord, - TipResponse, TipStatus, WalletBalance, WalletBalanceEntry, - WireFee, WireInfo, -} from "./types"; -import URI = require("urijs"); +} from "./walletTypes"; /** @@ -561,7 +569,9 @@ export namespace Stores { super("purchases", {keyPath: "contractTermsHash"}); } - fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillmentUrlIndex", "contractTerms.fulfillment_url"); + fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, + "fulfillmentUrlIndex", + "contractTerms.fulfillment_url"); orderIdIndex = new Index<string, PurchaseRecord>(this, "orderIdIndex", "contractTerms.order_id"); timestampIndex = new Index<string, PurchaseRecord>(this, "timestampIndex", "timestamp"); } @@ -1077,7 +1087,7 @@ export class Wallet { if (!sp) { return; } - if (sp.proposalId != proposalId) { + if (sp.proposalId !== proposalId) { return; } const coinKeys = sp.payCoinInfo.updatedCoins.map(x => x.coinPub); @@ -1090,8 +1100,8 @@ export class Wallet { if (!currentCoin) { return; } - if (Amounts.cmp(specCoin.currentAmount, currentCoin.currentAmount) != 0) { - return + if (Amounts.cmp(specCoin.currentAmount, currentCoin.currentAmount) !== 0) { + return; } } return sp; @@ -1135,7 +1145,7 @@ export class Wallet { } // Only create speculative signature if we don't already have one for this proposal - if ((!this.speculativePayData) || (this.speculativePayData && this.speculativePayData.proposalId != proposalId)) { + if ((!this.speculativePayData) || (this.speculativePayData && this.speculativePayData.proposalId !== proposalId)) { const { exchangeUrl, cds } = res; const payCoinInfo = await this.cryptoApi.signDeposit(proposal.contractTerms, cds); this.speculativePayData = { @@ -1250,7 +1260,7 @@ export class Wallet { .finish(); if (coin.status === CoinStatus.TainedByTip) { - let tip = await this.q().getIndexed(Stores.tips.coinPubIndex, coin.coinPub); + const tip = await this.q().getIndexed(Stores.tips.coinPubIndex, coin.coinPub); if (!tip) { throw Error(`inconsistent DB: tip for coin pub ${coin.coinPub} not found.`); } @@ -1263,8 +1273,8 @@ export class Wallet { c.status = CoinStatus.Fresh; } return c; - } - await this.q().mutate(Stores.coins, coin.coinPub, mutateCoin) + }; + await this.q().mutate(Stores.coins, coin.coinPub, mutateCoin); // Show notifications only for accepted tips this.badge.showNotification(); } @@ -1724,6 +1734,7 @@ export class Wallet { const ret: ReserveCreationInfo = { earliestDepositExpiration, exchangeInfo, + exchangeVersion: exchangeInfo.protocolVersion || "unknown", isAudited, isTrusted, numOfferedDenoms: possibleDenoms.length, @@ -1731,11 +1742,10 @@ export class Wallet { selectedDenoms, trustedAuditorPubs, versionMatch, + walletVersion: WALLET_PROTOCOL_VERSION, wireFees, wireInfo, withdrawFee: acc, - exchangeVersion: exchangeInfo.protocolVersion || "unknown", - walletVersion: WALLET_PROTOCOL_VERSION, }; return ret; } @@ -1779,7 +1789,7 @@ export class Wallet { .indexJoinLeft(Stores.denominations.exchangeBaseUrlIndex, (e) => e.exchangeBaseUrl) .fold((cd: JoinLeftResult<CoinRecord, DenominationRecord>, - suspendedCoins: CoinRecord[]) => { + suspendedCoins: CoinRecord[]) => { if ((!cd.right) || (!cd.right.isOffered)) { return Array.prototype.concat(suspendedCoins, [cd.left]); } @@ -1922,8 +1932,7 @@ export class Wallet { this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeInfo.baseUrl) .fold((x: DenominationRecord, - acc: typeof existingDenoms) => (acc[x.denomPub] = x, acc), - {}) + acc: typeof existingDenoms) => (acc[x.denomPub] = x, acc), {}) ); const newDenoms: typeof existingDenoms = {}; @@ -2432,9 +2441,9 @@ export class Wallet { for (const tip of tips) { history.push({ detail: { - merchantDomain: tip.merchantDomain, - amount: tip.amount, accepted: tip.accepted, + amount: tip.amount, + merchantDomain: tip.merchantDomain, tipId: tip.tipId, }, timestamp: tip.timestamp, @@ -2760,8 +2769,8 @@ export class Wallet { H_wire: coinsReturnRecord.contractTerms.H_wire, coin_pub: c.coinPaySig.coin_pub, coin_sig: c.coinPaySig.coin_sig, - denom_pub: c.coinPaySig.denom_pub, contribution: c.coinPaySig.contribution, + denom_pub: c.coinPaySig.denom_pub, h_contract_terms: coinsReturnRecord.contractTermsHash, merchant_pub: coinsReturnRecord.contractTerms.merchant_pub, pay_deadline: coinsReturnRecord.contractTerms.pay_deadline, @@ -2950,7 +2959,12 @@ export class Wallet { * Get planchets for a tip. Creates new planchets if they don't exist already * for this tip. The tip is uniquely identified by the merchant's domain and the tip id. */ - async getTipPlanchets(merchantDomain: string, tipId: string, amount: AmountJson, deadline: number, exchangeUrl: string, nextUrl: string): Promise<TipPlanchetDetail[]> { + async getTipPlanchets(merchantDomain: string, + tipId: string, + amount: AmountJson, + deadline: number, + exchangeUrl: string, + nextUrl: string): Promise<TipPlanchetDetail[]> { let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]); if (!tipRecord) { await this.updateExchangeFromUrl(exchangeUrl); @@ -2973,9 +2987,9 @@ export class Wallet { await this.q().put(Stores.tips, tipRecord).finish(); } // Planchets in the form that the merchant expects - const planchetDetail: TipPlanchetDetail[]= tipRecord.planchets.map((p) => ({ - denom_pub_hash: p.denomPubHash, + const planchetDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => ({ coin_ev: p.coinEv, + denom_pub_hash: p.denomPubHash, })); return planchetDetail; } @@ -2985,7 +2999,7 @@ export class Wallet { * These coins will not appear in the wallet yet. */ async processTipResponse(merchantDomain: string, tipId: string, response: TipResponse): Promise<void> { - let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]); + const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]); if (!tipRecord) { throw Error("tip not found"); } @@ -2995,18 +3009,18 @@ export class Wallet { } for (let i = 0; i < tipRecord.planchets.length; i++) { - let planchet = tipRecord.planchets[i]; - let preCoin = { - coinPub: planchet.coinPub, - coinPriv: planchet.coinPriv, + const planchet = tipRecord.planchets[i]; + const preCoin = { + blindingKey: planchet.blindingKey, coinEv: planchet.coinEv, + coinPriv: planchet.coinPriv, + coinPub: planchet.coinPub, coinValue: planchet.coinValue, - reservePub: response.reserve_pub, denomPub: planchet.denomPub, - blindingKey: planchet.blindingKey, - withdrawSig: response.reserve_sigs[i].reserve_sig, exchangeBaseUrl: tipRecord.exchangeUrl, isFromTip: true, + reservePub: response.reserve_pub, + withdrawSig: response.reserve_sigs[i].reserve_sig, }; await this.q().put(Stores.precoins, preCoin); this.processPreCoin(preCoin); @@ -3082,8 +3096,8 @@ export class Wallet { const gcProposal = (d: ProposalRecord, n: number) => { // Delete proposal after 60 minutes or 5 minutes before pay deadline, // whatever comes first. - let deadlinePayMilli = getTalerStampSec(d.contractTerms.pay_deadline)! * 1000; - let deadlineExpireMilli = nowMilli + (1000 * 60 * 60); + const deadlinePayMilli = getTalerStampSec(d.contractTerms.pay_deadline)! * 1000; + const deadlineExpireMilli = nowMilli + (1000 * 60 * 60); return d.timestamp < Math.min(deadlinePayMilli, deadlineExpireMilli); }; await this.q().deleteIf(Stores.proposals, gcProposal).finish(); @@ -3096,7 +3110,7 @@ export class Wallet { } activeExchanges.push(d.baseUrl); return false; - } + }; await this.q().deleteIf(Stores.exchanges, gcExchange).finish(); diff --git a/src/walletTypes.ts b/src/walletTypes.ts new file mode 100644 index 000000000..cd0eee4cc --- /dev/null +++ b/src/walletTypes.ts @@ -0,0 +1,572 @@ +/* + This file is part of TALER + (C) 2015-2017 GNUnet e.V. and INRIA + + 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. + */ + +/** + * Imports. + */ +import { Checkable } from "./checkable"; +import * as LibtoolVersion from "./libtoolVersion"; + +import { AmountJson } from "./amounts"; + +import { + CoinRecord, + DenominationRecord, + ExchangeRecord, + ExchangeWireFeesRecord, + TipRecord, +} from "./dbTypes"; +import { + CoinPaySig, + ContractTerms, + PayReq, + TipResponse, +} from "./talerTypes"; + + +/** + * Response for the create reserve request to the wallet. + */ +@Checkable.Class() +export class CreateReserveResponse { + /** + * Exchange URL where the bank should create the reserve. + * The URL is canonicalized in the response. + */ + @Checkable.String + exchange: string; + + /** + * Reserve public key of the newly created reserve. + */ + @Checkable.String + reservePub: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => CreateReserveResponse; +} + + +/** + * Wire info, sent to the bank when creating a reserve. Fee information will + * be filtered out. Only methods that the bank also supports should be sent. + */ +export interface WireInfo { + /** + * Mapping from wire method type to the exchange's wire info, + * excluding fees. + */ + [type: string]: any; +} + + +/** + * Information about what will happen when creating a reserve. + * + * Sent to the wallet frontend to be rendered and shown to the user. + */ +export interface ReserveCreationInfo { + /** + * Exchange that the reserve will be created at. + */ + exchangeInfo: ExchangeRecord; + + /** + * Filtered wire info to send to the bank. + */ + wireInfo: WireInfo; + + /** + * Selected denominations for withdraw. + */ + selectedDenoms: DenominationRecord[]; + + /** + * Fees for withdraw. + */ + withdrawFee: AmountJson; + + /** + * Remaining balance that is too small to be withdrawn. + */ + overhead: AmountJson; + + /** + * Wire fees from the exchange. + */ + wireFees: ExchangeWireFeesRecord; + + /** + * Does the wallet know about an auditor for + * the exchange that the reserve. + */ + isAudited: boolean; + + /** + * The exchange is trusted directly. + */ + isTrusted: boolean; + + /** + * The earliest deposit expiration of the selected coins. + */ + earliestDepositExpiration: number; + + /** + * 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; +} + + +/** + * Mapping from currency/exchange to detailed balance + * information. + */ +export interface WalletBalance { + /** + * Mapping from currency name to detailed balance info. + */ + byExchange: { [exchangeBaseUrl: string]: WalletBalanceEntry }; + + /** + * Mapping from currency name to detailed balance info. + */ + byCurrency: { [currency: string]: WalletBalanceEntry }; +} + + +/** + * Detailed wallet balance for a particular currency. + */ +export interface WalletBalanceEntry { + /** + * Directly available amount. + */ + available: AmountJson; + /** + * Amount that we're waiting for (refresh, withdrawal). + */ + pendingIncoming: AmountJson; + /** + * Amount that's marked for a pending payment. + */ + pendingPayment: AmountJson; + /** + * Amount that was paid back and we could withdraw again. + */ + paybackAmount: AmountJson; +} + + +/** + * Coins used for a payment, with signatures authorizing the payment and the + * coins with remaining value updated to accomodate for a payment. + */ +export interface PayCoinInfo { + originalCoins: CoinRecord[]; + updatedCoins: CoinRecord[]; + sigs: CoinPaySig[]; +} + + +/** + * Listener for notifications from the wallet. + */ +export interface Notifier { + /** + * Called when a new notification arrives. + */ + notify(): void; +} + + +/** + * For terseness. + */ +export function mkAmount(value: number, fraction: number, currency: string): AmountJson { + return {value, fraction, currency}; +} + + +/** + * Possible results for checkPay. + */ +export interface CheckPayResult { + status: "paid" | "payment-possible" | "insufficient-balance"; + coinSelection?: CoinSelectionResult; +} + + +/** + * Possible results for confirmPay. + */ +export type ConfirmPayResult = "paid" | "insufficient-balance"; + + +/** + * Activity history record. + */ +export interface HistoryRecord { + /** + * Type of the history event. + */ + type: string; + + /** + * Time when the activity was recorded. + */ + timestamp: number; + + /** + * Subject of the entry. Used to group multiple history records together. + * Only the latest history record with the same subjectId will be shown. + */ + subjectId?: string; + + /** + * Details used when rendering the history record. + */ + detail: any; +} + + +/** + * Response to a query payment request. Tagged union over the 'found' field. + */ +export type QueryPaymentResult = QueryPaymentNotFound | QueryPaymentFound; + + +/** + * Query payment response when the payment was found. + */ +export interface QueryPaymentNotFound { + found: false; +} + + +/** + * Query payment response when the payment wasn't found. + */ +export interface QueryPaymentFound { + found: true; + contractTermsHash: string; + contractTerms: ContractTerms; + payReq: PayReq; +} + + +/** + * 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 types stored in the wallet. + */ + senderWires: object[]; +} + + +/** + * Request to mark a reserve as confirmed. + */ +@Checkable.Class() +export class CreateReserveRequest { + /** + * The initial amount for the reserve. + */ + @Checkable.Value(AmountJson) + amount: AmountJson; + + /** + * Exchange URL where the bank should create the reserve. + */ + @Checkable.String + exchange: string; + + /** + * Wire details for the bank account that sent the funds to the exchange. + */ + @Checkable.Optional(Checkable.Any) + senderWire?: object; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => CreateReserveRequest; +} + + +/** + * Request to mark a reserve as confirmed. + */ +@Checkable.Class() +export class ConfirmReserveRequest { + /** + * Public key of then reserve that should be marked + * as confirmed. + */ + @Checkable.String + reservePub: string; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => ConfirmReserveRequest; +} + + +/** + * Wire coins to the user's own bank account. + */ +@Checkable.Class() +export class ReturnCoinsRequest { + /** + * The amount to wire. + */ + @Checkable.Value(AmountJson) + amount: AmountJson; + + /** + * The exchange to take the coins from. + */ + @Checkable.String + exchange: string; + + /** + * Wire details for the bank account of the customer that will + * receive the funds. + */ + @Checkable.Any + senderWire?: object; + + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ + static checked: (obj: any) => ReturnCoinsRequest; +} + + +/** + * Result of selecting coins, contains the exchange, and selected + * coins with their denomination. + */ +export interface CoinSelectionResult { + exchangeUrl: string; + cds: CoinWithDenom[]; + totalFees: AmountJson; +} + + +/** + * Named tuple of coin and denomination. + */ +export interface CoinWithDenom { + /** + * A coin. Must have the same denomination public key as the associated + * denomination. + */ + coin: CoinRecord; + /** + * An associated denomination. + */ + denom: DenominationRecord; +} + + +/** + * Status of processing a tip. + */ +export interface TipStatus { + tip: TipRecord; + rci?: ReserveCreationInfo; +} + + +/** + * Request to the wallet for the status of processing a tip. + */ +@Checkable.Class() +export class TipStatusRequest { + /** + * Identifier of the tip. + */ + @Checkable.String + tipId: string; + + /** + * Merchant domain. Within each merchant domain, the tip identifier + * uniquely identifies a tip. + */ + @Checkable.String + merchantDomain: string; + + /** + * Create a TipStatusRequest from untyped JSON. + */ + static checked: (obj: any) => TipStatusRequest; +} + +/** + * Request to the wallet to accept a tip. + */ +@Checkable.Class() +export class AcceptTipRequest { + /** + * Identifier of the tip. + */ + @Checkable.String + tipId: string; + + /** + * Merchant domain. Within each merchant domain, the tip identifier + * uniquely identifies a tip. + */ + @Checkable.String + merchantDomain: string; + + /** + * Create an AcceptTipRequest from untyped JSON. + * Validates the schema and throws on error. + */ + static checked: (obj: any) => AcceptTipRequest; +} + + +/** + * Request for the wallet to process a tip response from a merchant. + */ +@Checkable.Class() +export class ProcessTipResponseRequest { + /** + * Identifier of the tip. + */ + @Checkable.String + tipId: string; + + /** + * Merchant domain. Within each merchant domain, the tip identifier + * uniquely identifies a tip. + */ + @Checkable.String + merchantDomain: string; + + /** + * Tip response from the merchant. + */ + @Checkable.Value(TipResponse) + tipResponse: TipResponse; + + /** + * Create an AcceptTipRequest from untyped JSON. + * Validates the schema and throws on error. + */ + static checked: (obj: any) => ProcessTipResponseRequest; +} + + +/** + * Request for the wallet to generate tip planchets. + */ +@Checkable.Class() +export class GetTipPlanchetsRequest { + /** + * Identifier of the tip. + */ + @Checkable.String + tipId: string; + + /** + * Merchant domain. Within each merchant domain, the tip identifier + * uniquely identifies a tip. + */ + @Checkable.String + merchantDomain: string; + + /** + * Amount of the tip. + */ + @Checkable.Optional(Checkable.Value(AmountJson)) + amount: AmountJson; + + /** + * Deadline for picking up the tip. + */ + @Checkable.Number + deadline: number; + + /** + * Exchange URL that must be used to pick up the tip. + */ + @Checkable.String + exchangeUrl: string; + + /** + * URL to nagivate to after processing the tip. + */ + @Checkable.String + nextUrl: string; + + /** + * Create an AcceptTipRequest from untyped JSON. + * Validates the schema and throws on error. + */ + static checked: (obj: any) => GetTipPlanchetsRequest; +} diff --git a/src/webex/messages.ts b/src/webex/messages.ts index 44c9f166c..0d0329808 100644 --- a/src/webex/messages.ts +++ b/src/webex/messages.ts @@ -21,7 +21,10 @@ // Messages are already documented in wxApi. /* tslint:disable:completed-docs */ -import * as types from "../types"; +import { AmountJson } from "../amounts"; +import * as dbTypes from "../dbTypes"; +import * as talerTypes from "../talerTypes"; +import * as walletTypes from "../walletTypes"; /** * Message type information. @@ -29,7 +32,7 @@ import * as types from "../types"; export interface MessageMap { "balances": { request: { }; - response: types.WalletBalance; + response: walletTypes.WalletBalance; }; "dump-db": { request: { }; @@ -55,7 +58,7 @@ export interface MessageMap { }; "create-reserve": { request: { - amount: types.AmountJson; + amount: AmountJson; exchange: string }; response: void; @@ -70,11 +73,11 @@ export interface MessageMap { }; "confirm-pay": { request: { proposalId: number; }; - response: types.ConfirmPayResult; + response: walletTypes.ConfirmPayResult; }; "check-pay": { request: { proposalId: number; }; - response: types.CheckPayResult; + response: walletTypes.CheckPayResult; }; "query-payment": { request: { }; @@ -82,31 +85,31 @@ export interface MessageMap { }; "exchange-info": { request: { baseUrl: string }; - response: types.ExchangeRecord; + response: dbTypes.ExchangeRecord; }; "currency-info": { request: { name: string }; - response: types.CurrencyRecord; + response: dbTypes.CurrencyRecord; }; "hash-contract": { request: { contract: object }; response: string; }; "save-proposal": { - request: { proposal: types.ProposalRecord }; + request: { proposal: dbTypes.ProposalRecord }; response: void; }; "reserve-creation-info": { - request: { baseUrl: string, amount: types.AmountJson }; - response: types.ReserveCreationInfo; + request: { baseUrl: string, amount: AmountJson }; + response: walletTypes.ReserveCreationInfo; }; "get-history": { request: { }; - response: types.HistoryRecord[]; + response: walletTypes.HistoryRecord[]; }; "get-proposal": { request: { proposalId: number }; - response: types.ProposalRecord | undefined; + response: dbTypes.ProposalRecord | undefined; }; "get-coins": { request: { exchangeBaseUrl: string }; @@ -118,23 +121,23 @@ export interface MessageMap { }; "get-currencies": { request: { }; - response: types.CurrencyRecord[]; + response: dbTypes.CurrencyRecord[]; }; "update-currency": { - request: { currencyRecord: types.CurrencyRecord }; + request: { currencyRecord: dbTypes.CurrencyRecord }; response: void; }; "get-exchanges": { request: { }; - response: types.ExchangeRecord[]; + response: dbTypes.ExchangeRecord[]; }; "get-reserves": { request: { exchangeBaseUrl: string }; - response: types.ReserveRecord[]; + response: dbTypes.ReserveRecord[]; }; "get-payback-reserves": { request: { }; - response: types.ReserveRecord[]; + response: dbTypes.ReserveRecord[]; }; "withdraw-payback-reserve": { request: { reservePub: string }; @@ -142,11 +145,11 @@ export interface MessageMap { }; "get-precoins": { request: { exchangeBaseUrl: string }; - response: types.PreCoinRecord[]; + response: dbTypes.PreCoinRecord[]; }; "get-denoms": { request: { exchangeBaseUrl: string }; - response: types.DenominationRecord[]; + response: dbTypes.DenominationRecord[]; }; "payback-coin": { request: { coinPub: string }; @@ -189,23 +192,23 @@ export interface MessageMap { response: void; }; "get-full-refund-fees": { - request: { refundPermissions: types.RefundPermission[] }; + request: { refundPermissions: talerTypes.RefundPermission[] }; response: void; }; "get-tip-planchets": { - request: types.GetTipPlanchetsRequest; + request: walletTypes.GetTipPlanchetsRequest; response: void; }; "process-tip-response": { - request: types.ProcessTipResponseRequest; + request: walletTypes.ProcessTipResponseRequest; response: void; }; "accept-tip": { - request: types.AcceptTipRequest; + request: walletTypes.AcceptTipRequest; response: void; }; "get-tip-status": { - request: types.TipStatusRequest; + request: walletTypes.TipStatusRequest; response: void; }; "clear-notification": { diff --git a/src/webex/notify.ts b/src/webex/notify.ts index 05883e8bb..1a447c0ac 100644 --- a/src/webex/notify.ts +++ b/src/webex/notify.ts @@ -29,7 +29,8 @@ import URI = require("urijs"); import wxApi = require("./wxApi"); import { getTalerStampSec } from "../helpers"; -import { TipToken, QueryPaymentResult } from "../types"; +import { TipToken } from "../talerTypes"; +import { QueryPaymentResult } from "../walletTypes"; import axios from "axios"; @@ -272,7 +273,12 @@ function talerPay(msg: any): Promise<any> { const merchantDomain = new URI(document.location.href).origin(); let walletResp; try { - walletResp = await wxApi.getTipPlanchets(merchantDomain, tipToken.tip_id, tipToken.amount, deadlineSec, tipToken.exchange_url, tipToken.next_url); + walletResp = await wxApi.getTipPlanchets(merchantDomain, + tipToken.tip_id, + tipToken.amount, + deadlineSec, + tipToken.exchange_url, + tipToken.next_url); } catch (e) { wxApi.logAndDisplayError({ message: e.message, @@ -283,12 +289,12 @@ function talerPay(msg: any): Promise<any> { throw e; } - let planchets = walletResp; + const planchets = walletResp; if (!planchets) { wxApi.logAndDisplayError({ - message: "processing tip failed", detail: walletResp, + message: "processing tip failed", name: "tipping-failed", sameTab: true, }); diff --git a/src/webex/pages/add-auditor.tsx b/src/webex/pages/add-auditor.tsx index 4b898b13c..1ab6fdf9c 100644 --- a/src/webex/pages/add-auditor.tsx +++ b/src/webex/pages/add-auditor.tsx @@ -23,7 +23,7 @@ import { CurrencyRecord, -} from "../../types"; +} from "../../dbTypes"; import { ImplicitStateComponent, StateHolder } from "../components"; import { diff --git a/src/webex/pages/auditors.tsx b/src/webex/pages/auditors.tsx index 9d57218ad..276a7e8e1 100644 --- a/src/webex/pages/auditors.tsx +++ b/src/webex/pages/auditors.tsx @@ -25,7 +25,7 @@ import { AuditorRecord, CurrencyRecord, ExchangeForCurrencyRecord, -} from "../../types"; +} from "../../dbTypes"; import { getCurrencies, diff --git a/src/webex/pages/confirm-contract.tsx b/src/webex/pages/confirm-contract.tsx index e41b0a1df..83de738b9 100644 --- a/src/webex/pages/confirm-contract.tsx +++ b/src/webex/pages/confirm-contract.tsx @@ -24,12 +24,15 @@ * Imports. */ import * as i18n from "../../i18n"; + import { - CheckPayResult, - ContractTerms, ExchangeRecord, ProposalRecord, -} from "../../types"; +} from "../../dbTypes"; +import { ContractTerms } from "../../talerTypes"; +import { + CheckPayResult, +} from "../../walletTypes"; import { renderAmount } from "../renderHtml"; import * as wxApi from "../wxApi"; diff --git a/src/webex/pages/confirm-create-reserve.tsx b/src/webex/pages/confirm-create-reserve.tsx index 48bcd97c9..903975c6e 100644 --- a/src/webex/pages/confirm-create-reserve.tsx +++ b/src/webex/pages/confirm-create-reserve.tsx @@ -24,13 +24,17 @@ import { canonicalizeBaseUrl } from "../../helpers"; import * as i18n from "../../i18n"; + +import { AmountJson } from "../../amounts"; +import * as Amounts from "../../amounts"; + import { - AmountJson, - Amounts, - CreateReserveResponse, CurrencyRecord, +} from "../../dbTypes"; +import { + CreateReserveResponse, ReserveCreationInfo, -} from "../../types"; +} from "../../walletTypes"; import { ImplicitStateComponent, StateHolder } from "../components"; import { @@ -40,7 +44,10 @@ import { getReserveCreationInfo, } from "../wxApi"; -import { renderAmount, WithdrawDetailView } from "../renderHtml"; +import { + WithdrawDetailView, + renderAmount, +} from "../renderHtml"; import * as React from "react"; import * as ReactDOM from "react-dom"; @@ -78,8 +85,6 @@ class EventTrigger { } - - interface ExchangeSelectionProps { suggestedExchangeUrl: string; amount: AmountJson; @@ -273,7 +278,8 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { if (rci.versionMatch.currentCmp === -1) { return ( <p className="errorbox"> - Your wallet (protocol version <span>{rci.walletVersion}</span>) might be outdated. The exchange has a higher, incompatible + Your wallet (protocol version <span>{rci.walletVersion}</span>) might be outdated.<span> </span> + The exchange has a higher, incompatible protocol version (<span>{rci.exchangeVersion}</span>). </p> ); @@ -281,7 +287,8 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { if (rci.versionMatch.currentCmp === 1) { return ( <p className="errorbox"> - The chosen exchange (protocol version <span>{rci.exchangeVersion}</span> might be outdated. The exchange has a lower, incompatible + The chosen exchange (protocol version <span>{rci.exchangeVersion}</span> might be outdated.<span> </span> + The exchange has a lower, incompatible protocol version than your wallet (protocol version <span>{rci.walletVersion}</span>). </p> ); @@ -429,8 +436,8 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { amount_fraction: amount.fraction, amount_value: amount.value, exchange: resp.exchange, - reserve_pub: resp.reservePub, exchange_wire_details: JSON.stringify(filteredWireDetails), + reserve_pub: resp.reservePub, }; const url = new URI(callback_url).addQuery(q); if (!url.is("absolute")) { diff --git a/src/webex/pages/payback.tsx b/src/webex/pages/payback.tsx index a380a33d0..f69a33493 100644 --- a/src/webex/pages/payback.tsx +++ b/src/webex/pages/payback.tsx @@ -26,7 +26,7 @@ */ import { ReserveRecord, -} from "../../types"; +} from "../../dbTypes"; import { ImplicitStateComponent, StateHolder } from "../components"; import { renderAmount } from "../renderHtml"; diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx index ded430d2b..134ee6dea 100644 --- a/src/webex/pages/popup.tsx +++ b/src/webex/pages/popup.tsx @@ -26,13 +26,15 @@ * Imports. */ import * as i18n from "../../i18n"; + +import { AmountJson } from "../../amounts"; +import * as Amounts from "../../amounts"; + import { - AmountJson, - Amounts, HistoryRecord, WalletBalance, WalletBalanceEntry, -} from "../../types"; +} from "../../walletTypes"; import { abbrev, renderAmount } from "../renderHtml"; import * as wxApi from "../wxApi"; @@ -407,7 +409,8 @@ function formatHistoryItem(historyItem: HistoryRecord) { const url = tipPageUrl.query(params).href(); return ( <i18n.Translate wrap="p"> - Merchant <span>{d.merchantDomain}</span> gave a <a href={url} onClick={openTab(url)}> tip</a> of <span>{renderAmount(d.amount)}</span>. + Merchant <span>{d.merchantDomain}</span> gave + a <a href={url} onClick={openTab(url)}> tip</a> of <span>{renderAmount(d.amount)}</span>. <span> </span> { d.accepted ? null : <span>You did not accept the tip yet.</span> } </i18n.Translate> diff --git a/src/webex/pages/refund.tsx b/src/webex/pages/refund.tsx index e76fdfff3..3e82f3667 100644 --- a/src/webex/pages/refund.tsx +++ b/src/webex/pages/refund.tsx @@ -26,7 +26,10 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import URI = require("urijs"); -import * as types from "../../types"; +import * as dbTypes from "../../dbTypes"; + +import { AmountJson } from "../../amounts"; +import * as Amounts from "../../amounts"; import { AmountDisplay } from "../renderHtml"; import * as wxApi from "../wxApi"; @@ -36,14 +39,14 @@ interface RefundStatusViewProps { } interface RefundStatusViewState { - purchase?: types.PurchaseRecord; - refundFees?: types.AmountJson; + purchase?: dbTypes.PurchaseRecord; + refundFees?: AmountJson; gotResult: boolean; } interface RefundDetailProps { - purchase: types.PurchaseRecord; - fullRefundFees: types.AmountJson; + purchase: dbTypes.PurchaseRecord; + fullRefundFees: AmountJson; } const RefundDetail = ({purchase, fullRefundFees}: RefundDetailProps) => { @@ -59,13 +62,13 @@ const RefundDetail = ({purchase, fullRefundFees}: RefundDetailProps) => { throw Error("invariant"); } - let amountPending = types.Amounts.getZero(currency); + let amountPending = Amounts.getZero(currency); for (const k of pendingKeys) { - amountPending = types.Amounts.add(amountPending, purchase.refundsPending[k].refund_amount).amount; + amountPending = Amounts.add(amountPending, purchase.refundsPending[k].refund_amount).amount; } - let amountDone = types.Amounts.getZero(currency); + let amountDone = Amounts.getZero(currency); for (const k of doneKeys) { - amountDone = types.Amounts.add(amountDone, purchase.refundsDone[k].refund_amount).amount; + amountDone = Amounts.add(amountDone, purchase.refundsDone[k].refund_amount).amount; } const hasPending = amountPending.fraction !== 0 || amountPending.value !== 0; diff --git a/src/webex/pages/return-coins.tsx b/src/webex/pages/return-coins.tsx index 5bcb2252a..26db52ef4 100644 --- a/src/webex/pages/return-coins.tsx +++ b/src/webex/pages/return-coins.tsx @@ -25,12 +25,13 @@ * Imports. */ +import { AmountJson } from "../../amounts"; +import * as Amounts from "../../amounts"; + import { - AmountJson, - Amounts, SenderWireInfos, WalletBalance, -} from "../../types"; +} from "../../walletTypes"; import * as i18n from "../../i18n"; diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx index 678c0dfdd..7f96401c5 100644 --- a/src/webex/pages/tip.tsx +++ b/src/webex/pages/tip.tsx @@ -33,9 +33,13 @@ import { getTipStatus, } from "../wxApi"; -import { renderAmount, WithdrawDetailView } from "../renderHtml"; +import { + WithdrawDetailView, + renderAmount, +} from "../renderHtml"; -import { Amounts, TipStatus } from "../../types"; +import * as Amounts from "../../amounts"; +import { TipStatus } from "../../walletTypes"; interface TipDisplayProps { merchantDomain: string; @@ -54,7 +58,7 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { } async update() { - let tipStatus = await getTipStatus(this.props.merchantDomain, this.props.tipId); + const tipStatus = await getTipStatus(this.props.merchantDomain, this.props.tipId); this.setState({ tipStatus }); } @@ -73,7 +77,7 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { renderExchangeInfo(ts: TipStatus) { const rci = ts.rci; if (!rci) { - return <p>Waiting for info about exchange ...</p> + return <p>Waiting for info about exchange ...</p>; } const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; return ( @@ -102,7 +106,9 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { className="pure-button pure-button-primary" type="button" onClick={() => this.accept()}> - { this.state.working ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span> : null } + { this.state.working + ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span> + : null } Accept tip </button> {" "} @@ -119,7 +125,8 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> { return ( <div> <h2>Tip Received!</h2> - <p>You received a tip of <strong>{renderAmount(ts.tip.amount)}</strong> from <strong>{this.props.merchantDomain}</strong>.</p> + <p>You received a tip of <strong>{renderAmount(ts.tip.amount)}</strong> from <span> </span> + <strong>{this.props.merchantDomain}</strong>.</p> {ts.tip.accepted ? <p>You've accepted this tip! <a href={ts.tip.nextUrl}>Go back to merchant</a></p> : this.renderButtons() @@ -134,10 +141,10 @@ async function main() { try { const url = new URI(document.location.href); const query: any = URI.parseQuery(url.query()); - - let merchantDomain = query.merchant_domain; - let tipId = query.tip_id; - let props: TipDisplayProps = { tipId, merchantDomain }; + + const merchantDomain = query.merchant_domain; + const tipId = query.tip_id; + const props: TipDisplayProps = { tipId, merchantDomain }; ReactDOM.render(<TipDisplay {...props} />, document.getElementById("container")!); diff --git a/src/webex/pages/tree.tsx b/src/webex/pages/tree.tsx index 2ac0b8631..67e58a1df 100644 --- a/src/webex/pages/tree.tsx +++ b/src/webex/pages/tree.tsx @@ -22,6 +22,7 @@ import { getTalerStampDate } from "../../helpers"; + import { CoinRecord, CoinStatus, @@ -29,7 +30,7 @@ import { ExchangeRecord, PreCoinRecord, ReserveRecord, -} from "../../types"; +} from "../../dbTypes"; import { ImplicitStateComponent, StateHolder } from "../components"; import { diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx index d225cef0c..2e21932b0 100644 --- a/src/webex/renderHtml.tsx +++ b/src/webex/renderHtml.tsx @@ -24,12 +24,16 @@ /** * Imports. */ +import { AmountJson } from "../amounts"; +import * as Amounts from "../amounts"; + import { - AmountJson, - Amounts, DenominationRecord, +} from "../dbTypes"; +import { ReserveCreationInfo, -} from "../types"; +} from "../walletTypes"; + import { ImplicitStateComponent } from "./components"; @@ -239,7 +243,9 @@ function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element { ); } - +/** + * Shows details about a withdraw request. + */ export function WithdrawDetailView(props: {rci: ReserveCreationInfo | null}): JSX.Element { const rci = props.rci; return ( @@ -259,6 +265,9 @@ interface ExpanderTextProps { text: string; } +/** + * Show a heading with a toggle to show/hide the expandable content. + */ export class ExpanderText extends ImplicitStateComponent<ExpanderTextProps> { private expanded = this.makeState<boolean>(false); private textArea: any = undefined; diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index 2575eec90..2f7a13c48 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -22,26 +22,31 @@ /** * Imports. */ +import { AmountJson } from "../amounts"; import { - AmountJson, - CheckPayResult, CoinRecord, - ConfirmPayResult, CurrencyRecord, DenominationRecord, ExchangeRecord, PreCoinRecord, PurchaseRecord, + ReserveRecord, +} from "../dbTypes"; +import { + CheckPayResult, + ConfirmPayResult, QueryPaymentResult, - RefundPermission, ReserveCreationInfo, - ReserveRecord, SenderWireInfos, - TipResponse, - TipPlanchetDetail, TipStatus, WalletBalance, -} from "../types"; +} from "../walletTypes"; + +import { + RefundPermission, + TipPlanchetDetail, + TipResponse, +} from "../talerTypes"; import { MessageMap, MessageType } from "./messages"; @@ -366,22 +371,39 @@ export function getFullRefundFees(args: { refundPermissions: RefundPermission[] /** * Get or generate planchets to give the merchant that wants to tip us. */ -export function getTipPlanchets(merchantDomain: string, tipId: string, amount: AmountJson, deadline: number, exchangeUrl: string, nextUrl: string): Promise<TipPlanchetDetail[]> { +export function getTipPlanchets(merchantDomain: string, + tipId: string, + amount: AmountJson, + deadline: number, + exchangeUrl: string, + nextUrl: string): Promise<TipPlanchetDetail[]> { return callBackend("get-tip-planchets", { merchantDomain, tipId, amount, deadline, exchangeUrl, nextUrl }); } +/** + * Get the status of processing a tip. + */ export function getTipStatus(merchantDomain: string, tipId: string): Promise<TipStatus> { return callBackend("get-tip-status", { merchantDomain, tipId }); } +/** + * Mark a tip as accepted by the user. + */ export function acceptTip(merchantDomain: string, tipId: string): Promise<TipStatus> { return callBackend("accept-tip", { merchantDomain, tipId }); } +/** + * Process a response from the merchant for a tip request. + */ export function processTipResponse(merchantDomain: string, tipId: string, tipResponse: TipResponse): Promise<void> { return callBackend("process-tip-response", { merchantDomain, tipId, tipResponse }); } +/** + * Clear notifications that the wallet shows to the user. + */ export function clearNotification(): Promise<void> { return callBackend("clear-notification", { }); } diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index 213d234d4..a8ce5eebc 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -30,18 +30,21 @@ import { Index, Store, } from "../query"; + +import { AmountJson } from "../amounts"; + +import { ProposalRecord } from "../dbTypes"; import { AcceptTipRequest, - AmountJson, ConfirmReserveRequest, CreateReserveRequest, GetTipPlanchetsRequest, Notifier, ProcessTipResponseRequest, - ProposalRecord, ReturnCoinsRequest, TipStatusRequest, -} from "../types"; +} from "../walletTypes"; + import { Stores, WALLET_DB_VERSION, @@ -335,7 +338,12 @@ function handleMessage(sender: MessageSender, } case "get-tip-planchets": { const req = GetTipPlanchetsRequest.checked(detail); - return needsWallet().getTipPlanchets(req.merchantDomain, req.tipId, req.amount, req.deadline, req.exchangeUrl, req.nextUrl); + return needsWallet().getTipPlanchets(req.merchantDomain, + req.tipId, + req.amount, + req.deadline, + req.exchangeUrl, + req.nextUrl); } case "clear-notification": { return needsWallet().clearNotification(); @@ -702,11 +710,10 @@ export async function wxMain() { }); - // Clear notifications both when the popop opens, // as well when it closes. chrome.runtime.onConnect.addListener((port) => { - if (port.name == "popup") { + if (port.name === "popup") { if (currentWallet) { currentWallet.clearNotification(); } diff --git a/tooling/pogen/dumpTree.ts b/tooling/pogen/dumpTree.ts index 958c79416..af25caf32 100644 --- a/tooling/pogen/dumpTree.ts +++ b/tooling/pogen/dumpTree.ts @@ -21,12 +21,10 @@ * @author Florian Dold */ -/// <reference path="../decl/node.d.ts" /> - "use strict"; -import {readFileSync} from "fs"; -import {execSync} from "child_process"; +import { readFileSync } from "fs"; +import { execSync } from "child_process"; import * as ts from "typescript"; diff --git a/tsconfig.json b/tsconfig.json index d2a7f5526..ae77fb27c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "compileOnSave": true, "compilerOptions": { "target": "es6", "jsx": "react", @@ -7,8 +8,8 @@ "module": "commonjs", "sourceMap": true, "lib": [ - "ES6", - "DOM" + "es6", + "dom" ], "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, @@ -23,6 +24,7 @@ "decl/chrome/chrome.d.ts", "decl/jed.d.ts", "decl/urijs.d.ts", + "src/amounts.ts", "src/checkable.ts", "src/crypto/cryptoApi-test.ts", "src/crypto/cryptoApi.ts", @@ -34,6 +36,7 @@ "src/crypto/nodeWorker.ts", "src/crypto/nodeWorkerEntry.ts", "src/crypto/startWorker.js", + "src/dbTypes.ts", "src/helpers-test.ts", "src/helpers.ts", "src/http.ts", @@ -42,18 +45,13 @@ "src/libtoolVersion-test.ts", "src/libtoolVersion.ts", "src/logging.ts", - "src/memidb/aatree-test.ts", - "src/memidb/aatree.ts", - "src/memidb/memidb-test.ts", - "src/memidb/memidb.ts", - "src/memidb/w3c-wpt/abort-in-initial-upgradeneeded-test.ts", - "src/memidb/w3c-wpt/support.ts", "src/query.ts", + "src/talerTypes.ts", "src/timer.ts", "src/types-test.ts", - "src/types.ts", "src/wallet-test.ts", "src/wallet.ts", + "src/walletTypes.ts", "src/webex/background.ts", "src/webex/chromeBadge.ts", "src/webex/components.ts", diff --git a/tslint.json b/tslint.json index fbd1975b7..a56b56a94 100644 --- a/tslint.json +++ b/tslint.json @@ -5,6 +5,7 @@ ], "jsRules": {}, "rules": { + "arrow-parens": false, "max-line-length": { "options": [120] }, |