diff options
Diffstat (limited to 'src')
29 files changed, 2570 insertions, 2237 deletions
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();        }  | 
