aboutsummaryrefslogtreecommitdiff
path: root/src/types
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-12-12 20:53:15 +0100
committerFlorian Dold <florian.dold@gmail.com>2019-12-12 20:53:15 +0100
commit74433c3e05734aa1194049fcbcaa92c70ce61c74 (patch)
treed30e79c9ac3fd5720de628f6a9764354ec69c648 /src/types
parentcc137c87394ec34d2f54d69fe896dfdf3feec5ea (diff)
downloadwallet-core-74433c3e05734aa1194049fcbcaa92c70ce61c74.tar.xz
refactor: re-structure type definitions
Diffstat (limited to 'src/types')
-rw-r--r--src/types/dbTypes.ts1388
-rw-r--r--src/types/history.ts58
-rw-r--r--src/types/notifications.ts213
-rw-r--r--src/types/pending.ts161
-rw-r--r--src/types/talerTypes.ts944
-rw-r--r--src/types/types-test.ts164
-rw-r--r--src/types/walletTypes.ts512
7 files changed, 3440 insertions, 0 deletions
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
new file mode 100644
index 000000000..ce2bb4109
--- /dev/null
+++ b/src/types/dbTypes.ts
@@ -0,0 +1,1388 @@
+/*
+ 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 "../util/amounts";
+import { Checkable } from "../util/checkable";
+import {
+ Auditor,
+ CoinPaySig,
+ ContractTerms,
+ Denomination,
+ MerchantRefundPermission,
+ PayReq,
+ TipResponse,
+} from "./talerTypes";
+
+import { Index, Store } from "../util/query";
+import {
+ Timestamp,
+ OperationError,
+ Duration,
+ getTimestampNow,
+} from "./walletTypes";
+
+/**
+ * Current database version, should be incremented
+ * each time we do incompatible schema changes on the database.
+ * In the future we might consider adding migration functions for
+ * each version increment.
+ */
+export const WALLET_DB_VERSION = 28;
+
+export enum ReserveRecordStatus {
+ /**
+ * Waiting for manual confirmation.
+ */
+ UNCONFIRMED = "unconfirmed",
+
+ /**
+ * Reserve must be registered with the bank.
+ */
+ REGISTERING_BANK = "registering-bank",
+
+ /**
+ * We've registered reserve's information with the bank
+ * and are now waiting for the user to confirm the withdraw
+ * with the bank (typically 2nd factor auth).
+ */
+ WAIT_CONFIRM_BANK = "wait-confirm-bank",
+
+ /**
+ * Querying reserve status with the exchange.
+ */
+ QUERYING_STATUS = "querying-status",
+
+ /**
+ * Status is queried, the wallet must now select coins
+ * and start withdrawing.
+ */
+ WITHDRAWING = "withdrawing",
+
+ /**
+ * The corresponding withdraw record has been created.
+ * No further processing is done, unless explicitly requested
+ * by the user.
+ */
+ DORMANT = "dormant",
+}
+
+export interface RetryInfo {
+ firstTry: Timestamp;
+ nextRetry: Timestamp;
+ retryCounter: number;
+ active: boolean;
+}
+
+export interface RetryPolicy {
+ readonly backoffDelta: Duration;
+ readonly backoffBase: number;
+}
+
+const defaultRetryPolicy: RetryPolicy = {
+ backoffBase: 1.5,
+ backoffDelta: { d_ms: 200 },
+};
+
+export function updateRetryInfoTimeout(
+ r: RetryInfo,
+ p: RetryPolicy = defaultRetryPolicy,
+): void {
+ const now = getTimestampNow();
+ const t =
+ now.t_ms + p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter);
+ r.nextRetry = { t_ms: t };
+}
+
+export function initRetryInfo(
+ active: boolean = true,
+ p: RetryPolicy = defaultRetryPolicy,
+): RetryInfo {
+ if (!active) {
+ return {
+ active: false,
+ firstTry: { t_ms: Number.MAX_SAFE_INTEGER },
+ nextRetry: { t_ms: Number.MAX_SAFE_INTEGER },
+ retryCounter: 0,
+ };
+ }
+ const info = {
+ firstTry: getTimestampNow(),
+ active: true,
+ nextRetry: { t_ms: 0 },
+ retryCounter: 0,
+ };
+ updateRetryInfoTimeout(info, p);
+ return info;
+}
+
+/**
+ * A reserve record as stored in the wallet's database.
+ */
+export interface ReserveRecord {
+ /**
+ * The reserve public key.
+ */
+ reservePub: string;
+
+ /**
+ * The reserve private key.
+ */
+ reservePriv: string;
+
+ /**
+ * The exchange base URL.
+ */
+ exchangeBaseUrl: string;
+
+ /**
+ * Time when the reserve was created.
+ */
+ created: Timestamp;
+
+ /**
+ * Time when the information about this reserve was posted to the bank.
+ *
+ * Only applies if bankWithdrawStatusUrl is defined.
+ *
+ * Set to 0 if that hasn't happened yet.
+ */
+ timestampReserveInfoPosted: Timestamp | undefined;
+
+ /**
+ * Time when the reserve was confirmed.
+ *
+ * Set to 0 if not confirmed yet.
+ */
+ timestampConfirmed: Timestamp | undefined;
+
+ /**
+ * Amount that's still available for withdrawing
+ * from this reserve.
+ */
+ withdrawRemainingAmount: AmountJson;
+
+ /**
+ * Amount allocated for withdrawing.
+ * The corresponding withdraw operation may or may not
+ * have been completed yet.
+ */
+ withdrawAllocatedAmount: AmountJson;
+
+ withdrawCompletedAmount: AmountJson;
+
+ /**
+ * Amount requested when the reserve was created.
+ * When a reserve is re-used (rare!) the current_amount can
+ * be higher than the requested_amount
+ */
+ initiallyRequestedAmount: AmountJson;
+
+ /**
+ * We got some payback to this reserve. We'll cease to automatically
+ * withdraw money from it.
+ */
+ hasPayback: boolean;
+
+ /**
+ * Wire information (as payto URI) for the bank account that
+ * transfered funds for this reserve.
+ */
+ senderWire?: string;
+
+ /**
+ * Wire information (as payto URI) for the exchange, specifically
+ * the account that was transferred to when creating the reserve.
+ */
+ exchangeWire: string;
+
+ bankWithdrawStatusUrl?: string;
+
+ /**
+ * URL that the bank gave us to redirect the customer
+ * to in order to confirm a withdrawal.
+ */
+ bankWithdrawConfirmUrl?: string;
+
+ reserveStatus: ReserveRecordStatus;
+
+ /**
+ * Time of the last successful status query.
+ */
+ lastSuccessfulStatusQuery: Timestamp | undefined;
+
+ /**
+ * Retry info. This field is present even if no retry is scheduled,
+ * because we need it to be present for the index on the object store
+ * to work.
+ */
+ retryInfo: RetryInfo;
+
+ /**
+ * Last error that happened in a reserve operation
+ * (either talking to the bank or the exchange).
+ */
+ lastError: OperationError | undefined;
+}
+
+/**
+ * Auditor record as stored with currencies in the exchange database.
+ */
+export interface AuditorRecord {
+ /**
+ * Base url of the auditor.
+ */
+ baseUrl: string;
+ /**
+ * Public signing key of the auditor.
+ */
+ auditorPub: string;
+ /**
+ * Time when the auditing expires.
+ */
+ expirationStamp: number;
+}
+
+/**
+ * Exchange for currencies as stored in the wallet's currency
+ * information database.
+ */
+export interface ExchangeForCurrencyRecord {
+ /**
+ * FIXME: unused?
+ */
+ 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.Value(() => Timestamp)
+ stampStart: Timestamp;
+
+ /**
+ * Date after which the currency can't be withdrawn anymore.
+ */
+ @Checkable.Value(() => Timestamp)
+ stampExpireWithdraw: Timestamp;
+
+ /**
+ * Date after the denomination officially doesn't exist anymore.
+ */
+ @Checkable.Value(() => Timestamp)
+ stampExpireLegal: Timestamp;
+
+ /**
+ * Data after which coins of this denomination can't be deposited anymore.
+ */
+ @Checkable.Value(() => Timestamp)
+ stampExpireDeposit: Timestamp;
+
+ /**
+ * 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;
+}
+
+/**
+ * Details about the exchange that we only know after
+ * querying /keys and /wire.
+ */
+export interface ExchangeDetails {
+ /**
+ * Master public key of the exchange.
+ */
+ masterPublicKey: string;
+ /**
+ * Auditors (partially) auditing the exchange.
+ */
+ auditors: Auditor[];
+
+ /**
+ * Currency that the exchange offers.
+ */
+ currency: string;
+
+ /**
+ * Last observed protocol version.
+ */
+ protocolVersion: string;
+
+ /**
+ * Timestamp for last update.
+ */
+ lastUpdateTime: Timestamp;
+}
+
+export const enum ExchangeUpdateStatus {
+ FETCH_KEYS = "fetch_keys",
+ FETCH_WIRE = "fetch_wire",
+ FETCH_TERMS = "fetch_terms",
+ FINISHED = "finished",
+}
+
+export interface ExchangeBankAccount {
+ url: string;
+}
+
+export interface ExchangeWireInfo {
+ feesForType: { [wireMethod: string]: WireFee[] };
+ accounts: ExchangeBankAccount[];
+}
+
+/**
+ * Exchange record as stored in the wallet's database.
+ */
+export interface ExchangeRecord {
+ /**
+ * Base url of the exchange.
+ */
+ baseUrl: string;
+
+ /**
+ * Details, once known.
+ */
+ details: ExchangeDetails | undefined;
+
+ /**
+ * Mapping from wire method type to the wire fee.
+ */
+ wireInfo: ExchangeWireInfo | undefined;
+
+ /**
+ * When was the exchange added to the wallet?
+ */
+ timestampAdded: Timestamp;
+
+ /**
+ * Terms of service text or undefined if not downloaded yet.
+ */
+ termsOfServiceText: string | undefined;
+
+ /**
+ * ETag for last terms of service download.
+ */
+ termsOfServiceLastEtag: string | undefined;
+
+ /**
+ * ETag for last terms of service download.
+ */
+ termsOfServiceAcceptedEtag: string | undefined;
+
+ /**
+ * ETag for last terms of service download.
+ */
+ termsOfServiceAcceptedTimestamp: Timestamp | undefined;
+
+ /**
+ * Time when the update to the exchange has been started or
+ * undefined if no update is in progress.
+ */
+ updateStarted: Timestamp | undefined;
+ updateStatus: ExchangeUpdateStatus;
+ updateReason?: "initial" | "forced";
+
+ lastError?: OperationError;
+}
+
+/**
+ * A coin that isn't yet signed by an exchange.
+ */
+export interface PlanchetRecord {
+ /**
+ * Public key of the coin.
+ */
+ coinPub: string;
+ coinPriv: string;
+ /**
+ * Public key of the reserve, this might be a reserve not
+ * known to the wallet if the planchet is from a tip.
+ */
+ reservePub: string;
+ denomPubHash: string;
+ denomPub: string;
+ blindingKey: string;
+ withdrawSig: string;
+ coinEv: string;
+ coinValue: AmountJson;
+ isFromTip: boolean;
+}
+
+/**
+ * Planchet for a coin during refrehs.
+ */
+export interface RefreshPlanchetRecord {
+ /**
+ * Public key for the coin.
+ */
+ publicKey: string;
+ /**
+ * Private key for the coin.
+ */
+ privateKey: string;
+ /**
+ * Blinded public key.
+ */
+ coinEv: string;
+ /**
+ * Blinding key used.
+ */
+ blindingKey: string;
+}
+
+/**
+ * Status of a coin.
+ */
+export enum CoinStatus {
+ /**
+ * Withdrawn and never shown to anybody.
+ */
+ Fresh = "fresh",
+ /**
+ * Used for a completed transaction and now dirty.
+ */
+ Dirty = "dirty",
+ /**
+ * A coin that has been spent and refreshed.
+ */
+ Dormant = "dormant",
+}
+
+export enum CoinSource {
+ Withdraw = "withdraw",
+ Refresh = "refresh",
+ Tip = "tip",
+}
+
+/**
+ * CoinRecord as stored in the "coins" data store
+ * of the wallet database.
+ */
+export interface CoinRecord {
+ /**
+ * Withdraw session ID, or "" (empty string) if withdrawn via refresh.
+ */
+ withdrawSessionId: string;
+
+ /**
+ * Index of the coin in the withdrawal session.
+ */
+ coinIndex: number;
+
+ /**
+ * Public key of the coin.
+ */
+ coinPub: string;
+
+ /**
+ * Private key to authorize operations on the coin.
+ */
+ coinPriv: string;
+
+ /**
+ * Key used by the exchange used to sign the coin.
+ */
+ denomPub: string;
+
+ /**
+ * Hash of the public key that signs the coin.
+ */
+ denomPubHash: string;
+
+ /**
+ * Unblinded signature by the exchange.
+ */
+ denomSig: string;
+
+ /**
+ * Amount that's left on the coin.
+ */
+ currentAmount: AmountJson;
+
+ /**
+ * Base URL that identifies the exchange from which we got the
+ * coin.
+ */
+ exchangeBaseUrl: string;
+
+ /**
+ * 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;
+}
+
+export enum ProposalStatus {
+ /**
+ * Not downloaded yet.
+ */
+ DOWNLOADING = "downloading",
+ /**
+ * Proposal downloaded, but the user needs to accept/reject it.
+ */
+ PROPOSED = "proposed",
+ /**
+ * The user has accepted the proposal.
+ */
+ ACCEPTED = "accepted",
+ /**
+ * The user has rejected the proposal.
+ */
+ REJECTED = "rejected",
+ /**
+ * Downloaded proposal was detected as a re-purchase.
+ */
+ REPURCHASE = "repurchase",
+}
+
+@Checkable.Class()
+export class ProposalDownload {
+ /**
+ * 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;
+
+ /**
+ * Signature by the merchant over the contract details.
+ */
+ @Checkable.String()
+ contractTermsHash: string;
+}
+
+/**
+ * Record for a downloaded order, stored in the wallet's database.
+ */
+@Checkable.Class()
+export class ProposalRecord {
+ @Checkable.String()
+ orderId: string;
+
+ @Checkable.String()
+ merchantBaseUrl: string;
+
+ /**
+ * Downloaded data from the merchant.
+ */
+ download: ProposalDownload | undefined;
+
+ /**
+ * Unique ID when the order is stored in the wallet DB.
+ */
+ @Checkable.String()
+ proposalId: string;
+
+ /**
+ * Timestamp (in ms) of when the record
+ * was created.
+ */
+ @Checkable.Number()
+ timestamp: Timestamp;
+
+ /**
+ * Private key for the nonce.
+ */
+ @Checkable.String()
+ noncePriv: string;
+
+ /**
+ * Public key for the nonce.
+ */
+ @Checkable.String()
+ noncePub: string;
+
+ @Checkable.String()
+ proposalStatus: ProposalStatus;
+
+ @Checkable.String()
+ repurchaseProposalId: string | undefined;
+
+ /**
+ * Session ID we got when downloading the contract.
+ */
+ @Checkable.Optional(Checkable.String())
+ downloadSessionId?: string;
+
+ /**
+ * Retry info, even present when the operation isn't active to allow indexing
+ * on the next retry timestamp.
+ */
+ retryInfo: RetryInfo;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => ProposalRecord;
+
+ lastError: OperationError | undefined;
+}
+
+/**
+ * Status of a tip we got from a merchant.
+ */
+export interface TipRecord {
+ lastError: OperationError | undefined;
+ /**
+ * Has the user accepted the tip? Only after the tip has been accepted coins
+ * withdrawn from the tip may be used.
+ */
+ accepted: boolean;
+
+ /**
+ * Have we picked up the tip record from the merchant already?
+ */
+ pickedUp: boolean;
+
+ /**
+ * The tipped amount.
+ */
+ amount: AmountJson;
+
+ totalFees: AmountJson;
+
+ /**
+ * Timestamp, the tip can't be picked up anymore after this deadline.
+ */
+ deadline: Timestamp;
+
+ /**
+ * The exchange that will sign our coins, chosen by the merchant.
+ */
+ exchangeUrl: string;
+
+ /**
+ * Base URL of the merchant that is giving us the tip.
+ */
+ merchantBaseUrl: string;
+
+ /**
+ * Planchets, the members included in TipPlanchetDetail will be sent to the
+ * merchant.
+ */
+ planchets?: TipPlanchet[];
+
+ /**
+ * Response if the merchant responded,
+ * undefined otherwise.
+ */
+ response?: TipResponse[];
+
+ /**
+ * Tip ID chosen by the wallet.
+ */
+ tipId: string;
+
+ /**
+ * The merchant's identifier for this tip.
+ */
+ merchantTipId: string;
+
+ /**
+ * URL to go to once the tip has been accepted.
+ */
+ nextUrl?: string;
+
+ createdTimestamp: Timestamp;
+
+ /**
+ * Retry info, even present when the operation isn't active to allow indexing
+ * on the next retry timestamp.
+ */
+ retryInfo: RetryInfo;
+}
+
+/**
+ * Ongoing refresh
+ */
+export interface RefreshSessionRecord {
+ lastError: OperationError | undefined;
+
+ /**
+ * 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[];
+
+ /**
+ * Planchets for each cut-and-choose instance.
+ */
+ planchetsForGammas: RefreshPlanchetRecord[][];
+
+ /**
+ * 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;
+
+ /**
+ * Timestamp when the refresh session finished.
+ */
+ finishedTimestamp: Timestamp | undefined;
+
+ /**
+ * A 32-byte base32-crockford encoded random identifier.
+ */
+ refreshSessionId: string;
+
+ /**
+ * When has this refresh session been created?
+ */
+ created: Timestamp;
+
+ /**
+ * Retry info, even present when the operation isn't active to allow indexing
+ * on the next retry timestamp.
+ */
+ retryInfo: RetryInfo;
+}
+
+/**
+ * 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: Timestamp;
+
+ /**
+ * End date of the fee.
+ */
+ endStamp: Timestamp;
+
+ /**
+ * 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 {
+ /**
+ * Proposal ID for this purchase. Uniquely identifies the
+ * purchase and the proposal.
+ */
+ proposalId: string;
+
+ /**
+ * Hash of the contract terms.
+ */
+ contractTermsHash: string;
+
+ /**
+ * Contract terms we got from the merchant.
+ */
+ contractTerms: ContractTerms;
+
+ /**
+ * The payment request, ready to be send to the merchant's
+ * /pay URL.
+ */
+ payReq: PayReq;
+
+ /**
+ * Signature from the merchant over the contract terms.
+ */
+ merchantSig: string;
+
+ firstSuccessfulPayTimestamp: Timestamp | undefined;
+
+ /**
+ * Pending refunds for the purchase.
+ */
+ refundsPending: { [refundSig: string]: MerchantRefundPermission };
+
+ /**
+ * Submitted refunds for the purchase.
+ */
+ refundsDone: { [refundSig: string]: MerchantRefundPermission };
+
+ /**
+ * When was the purchase made?
+ * Refers to the time that the user accepted.
+ */
+ acceptTimestamp: Timestamp;
+
+ /**
+ * When was the last refund made?
+ * Set to 0 if no refund was made on the purchase.
+ */
+ lastRefundStatusTimestamp: Timestamp | undefined;
+
+ /**
+ * Last session signature that we submitted to /pay (if any).
+ */
+ lastSessionId: string | undefined;
+
+ /**
+ * Set for the first payment, or on re-plays.
+ */
+ paymentSubmitPending: boolean;
+
+ /**
+ * Do we need to query the merchant for the refund status
+ * of the payment?
+ */
+ refundStatusRequested: boolean;
+
+ /**
+ * An abort (with refund) was requested for this (incomplete!) purchase.
+ */
+ abortRequested: boolean;
+
+ /**
+ * The abort (with refund) was completed for this (incomplete!) purchase.
+ */
+ abortDone: boolean;
+
+ payRetryInfo: RetryInfo;
+
+ lastPayError: OperationError | undefined;
+
+ /**
+ * Retry information for querying the refund status with the merchant.
+ */
+ refundStatusRetryInfo: RetryInfo;
+
+ /**
+ * Last error (or undefined) for querying the refund status with the merchant.
+ */
+ lastRefundStatusError: OperationError | undefined;
+
+ /**
+ * Retry information for querying the refund status with the merchant.
+ */
+ refundApplyRetryInfo: RetryInfo;
+
+ /**
+ * Last error (or undefined) for querying the refund status with the merchant.
+ */
+ lastRefundApplyError: OperationError | undefined;
+
+ /**
+ * Continue querying the refund status until this deadline has expired.
+ */
+ autoRefundDeadline: Timestamp | undefined;
+}
+
+/**
+ * Information about wire information for bank accounts we withdrew coins from.
+ */
+export interface SenderWireRecord {
+ paytoUri: string;
+}
+
+/**
+ * Configuration key/value entries to configure
+ * the wallet.
+ */
+export interface ConfigRecord {
+ key: string;
+ value: any;
+}
+
+/**
+ * Coin that we're depositing ourselves.
+ */
+export interface DepositCoin {
+ coinPaySig: CoinPaySig;
+
+ /**
+ * Undefined if coin not deposited, otherwise signature
+ * from the exchange confirming the deposit.
+ */
+ depositedSig?: string;
+}
+
+/**
+ * Record stored in the wallet's database when the user sends coins back to
+ * their own bank account. Stores the status of coins that are deposited to
+ * the wallet itself, where the wallet acts as a "merchant" for the customer.
+ */
+export interface CoinsReturnRecord {
+ /**
+ * Hash of the contract for sending coins to our own bank account.
+ */
+ contractTermsHash: string;
+
+ contractTerms: ContractTerms;
+
+ /**
+ * Private key where corresponding
+ * public key is used in the contract terms
+ * as merchant pub.
+ */
+ merchantPriv: string;
+
+ coins: DepositCoin[];
+
+ /**
+ * Exchange base URL to deposit coins at.
+ */
+ exchange: string;
+
+ /**
+ * Our own wire information for the deposit.
+ */
+ wire: any;
+}
+
+export interface WithdrawalSourceTip {
+ type: "tip";
+ tipId: string;
+}
+
+export interface WithdrawalSourceReserve {
+ type: "reserve";
+ reservePub: string;
+}
+
+export type WithdrawalSource = WithdrawalSourceTip | WithdrawalSourceReserve;
+
+export interface WithdrawalSessionRecord {
+ withdrawSessionId: string;
+
+ source: WithdrawalSource;
+
+ exchangeBaseUrl: string;
+
+ /**
+ * When was the withdrawal operation started started?
+ * Timestamp in milliseconds.
+ */
+ startTimestamp: Timestamp;
+
+ /**
+ * When was the withdrawal operation completed?
+ */
+ finishTimestamp?: Timestamp;
+
+ totalCoinValue: AmountJson;
+
+ /**
+ * Amount including fees (i.e. the amount subtracted from the
+ * reserve to withdraw all coins in this withdrawal session).
+ */
+ rawWithdrawalAmount: AmountJson;
+
+ denoms: string[];
+
+ planchets: (undefined | PlanchetRecord)[];
+
+ /**
+ * Coins in this session that are withdrawn are set to true.
+ */
+ withdrawn: boolean[];
+
+ /**
+ * Retry info, always present even on completed operations so that indexing works.
+ */
+ retryInfo: RetryInfo;
+
+ /**
+ * Last error per coin/planchet, or undefined if no error occured for
+ * the coin/planchet.
+ */
+ lastCoinErrors: (OperationError | undefined)[];
+
+ lastError: OperationError | undefined;
+}
+
+export interface BankWithdrawUriRecord {
+ /**
+ * The withdraw URI we got from the bank.
+ */
+ talerWithdrawUri: string;
+
+ /**
+ * Reserve that was created for the withdraw URI.
+ */
+ reservePub: string;
+}
+
+/* tslint:disable:completed-docs */
+
+/**
+ * The stores and indices for the wallet database.
+ */
+export namespace Stores {
+ class ExchangesStore extends Store<ExchangeRecord> {
+ constructor() {
+ super("exchanges", { keyPath: "baseUrl" });
+ }
+ }
+
+ class CoinsStore extends Store<CoinRecord> {
+ constructor() {
+ super("coins", { keyPath: "coinPub" });
+ }
+
+ exchangeBaseUrlIndex = new Index<string, CoinRecord>(
+ this,
+ "exchangeBaseUrl",
+ "exchangeBaseUrl",
+ );
+ denomPubIndex = new Index<string, CoinRecord>(
+ this,
+ "denomPubIndex",
+ "denomPub",
+ );
+ byWithdrawalWithIdx = new Index<any, CoinRecord>(
+ this,
+ "planchetsByWithdrawalWithIdxIndex",
+ ["withdrawSessionId", "coinIndex"],
+ );
+ }
+
+ class ProposalsStore extends Store<ProposalRecord> {
+ constructor() {
+ super("proposals", { keyPath: "proposalId" });
+ }
+ urlAndOrderIdIndex = new Index<string, ProposalRecord>(this, "urlIndex", [
+ "merchantBaseUrl",
+ "orderId",
+ ]);
+ }
+
+ class PurchasesStore extends Store<PurchaseRecord> {
+ constructor() {
+ super("purchases", { keyPath: "proposalId" });
+ }
+
+ fulfillmentUrlIndex = new Index<string, PurchaseRecord>(
+ this,
+ "fulfillmentUrlIndex",
+ "contractTerms.fulfillment_url",
+ );
+ orderIdIndex = new Index<string, PurchaseRecord>(this, "orderIdIndex", [
+ "contractTerms.merchant_base_url",
+ "contractTerms.order_id",
+ ]);
+ }
+
+ class DenominationsStore extends Store<DenominationRecord> {
+ constructor() {
+ // cast needed because of bug in type annotations
+ super("denominations", {
+ keyPath: (["exchangeBaseUrl", "denomPub"] as any) as IDBKeyPath,
+ });
+ }
+
+ denomPubHashIndex = new Index<string, DenominationRecord>(
+ this,
+ "denomPubHashIndex",
+ "denomPubHash",
+ );
+ exchangeBaseUrlIndex = new Index<string, DenominationRecord>(
+ this,
+ "exchangeBaseUrlIndex",
+ "exchangeBaseUrl",
+ );
+ denomPubIndex = new Index<string, DenominationRecord>(
+ this,
+ "denomPubIndex",
+ "denomPub",
+ );
+ }
+
+ class CurrenciesStore extends Store<CurrencyRecord> {
+ constructor() {
+ super("currencies", { keyPath: "name" });
+ }
+ }
+
+ class ConfigStore extends Store<ConfigRecord> {
+ constructor() {
+ super("config", { keyPath: "key" });
+ }
+ }
+
+ class ReservesStore extends Store<ReserveRecord> {
+ constructor() {
+ super("reserves", { keyPath: "reservePub" });
+ }
+ }
+
+ class TipsStore extends Store<TipRecord> {
+ constructor() {
+ super("tips", { keyPath: "tipId" });
+ }
+ }
+
+ class SenderWiresStore extends Store<SenderWireRecord> {
+ constructor() {
+ super("senderWires", { keyPath: "paytoUri" });
+ }
+ }
+
+ class WithdrawalSessionsStore extends Store<WithdrawalSessionRecord> {
+ constructor() {
+ super("withdrawals", { keyPath: "withdrawSessionId" });
+ }
+ }
+
+ class BankWithdrawUrisStore extends Store<BankWithdrawUriRecord> {
+ constructor() {
+ super("bankWithdrawUris", { keyPath: "talerWithdrawUri" });
+ }
+ }
+
+ export const coins = new CoinsStore();
+ export const coinsReturns = new Store<CoinsReturnRecord>("coinsReturns", {
+ keyPath: "contractTermsHash",
+ });
+ export const config = new ConfigStore();
+ export const currencies = new CurrenciesStore();
+ export const denominations = new DenominationsStore();
+ export const exchanges = new ExchangesStore();
+ export const proposals = new ProposalsStore();
+ export const refresh = new Store<RefreshSessionRecord>("refresh", {
+ keyPath: "refreshSessionId",
+ });
+ export const reserves = new ReservesStore();
+ export const purchases = new PurchasesStore();
+ export const tips = new TipsStore();
+ export const senderWires = new SenderWiresStore();
+ export const withdrawalSession = new WithdrawalSessionsStore();
+ export const bankWithdrawUris = new BankWithdrawUrisStore();
+}
+
+/* tslint:enable:completed-docs */
diff --git a/src/types/history.ts b/src/types/history.ts
new file mode 100644
index 000000000..e925b0ffe
--- /dev/null
+++ b/src/types/history.ts
@@ -0,0 +1,58 @@
+import { Timestamp } from "./walletTypes";
+
+/*
+ This file is part of GNU Taler
+ (C) 2019 GNUnet e.V.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Type and schema definitions for the wallet's history.
+ */
+
+/**
+ * Activity history record.
+ */
+export interface HistoryEvent {
+ /**
+ * Type of the history event.
+ */
+ type: string;
+
+ /**
+ * Time when the activity was recorded.
+ */
+ timestamp: Timestamp;
+
+ /**
+ * Details used when rendering the history record.
+ */
+ detail: any;
+
+ /**
+ * Set to 'true' if the event has been explicitly created,
+ * and set to 'false' if the event has been derived from the
+ * state of the database.
+ */
+ explicit: boolean;
+}
+
+
+export interface HistoryQuery {
+ /**
+ * Verbosity of history events.
+ * Level 0: Only withdraw, pay, tip and refund events.
+ * Level 1: All events.
+ */
+ level: number;
+} \ No newline at end of file
diff --git a/src/types/notifications.ts b/src/types/notifications.ts
new file mode 100644
index 000000000..c64d33bfb
--- /dev/null
+++ b/src/types/notifications.ts
@@ -0,0 +1,213 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 GNUnet e.V.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Type and schema definitions for notifications from the wallet to clients
+ * of the wallet.
+ */
+
+export const enum NotificationType {
+ CoinWithdrawn = "coin-withdrawn",
+ ProposalAccepted = "proposal-accepted",
+ ProposalDownloaded = "proposal-downloaded",
+ RefundsSubmitted = "refunds-submitted",
+ PaybackStarted = "payback-started",
+ PaybackFinished = "payback-finished",
+ RefreshRevealed = "refresh-revealed",
+ RefreshMelted = "refresh-melted",
+ RefreshStarted = "refresh-started",
+ RefreshRefused = "refresh-refused",
+ ReserveUpdated = "reserve-updated",
+ ReserveConfirmed = "reserve-confirmed",
+ ReserveDepleted = "reserve-depleted",
+ ReserveCreated = "reserve-created",
+ WithdrawSessionCreated = "withdraw-session-created",
+ WithdrawSessionFinished = "withdraw-session-finished",
+ WaitingForRetry = "waiting-for-retry",
+ RefundStarted = "refund-started",
+ RefundQueried = "refund-queried",
+ RefundFinished = "refund-finished",
+ ExchangeOperationError = "exchange-operation-error",
+ RefreshOperationError = "refresh-operation-error",
+ RefundApplyOperationError = "refund-apply-error",
+ RefundStatusOperationError = "refund-status-error",
+ ProposalOperationError = "proposal-error",
+ TipOperationError = "tip-error",
+ PayOperationError = "pay-error",
+ WithdrawOperationError = "withdraw-error",
+ ReserveOperationError = "reserve-error",
+ Wildcard = "wildcard",
+}
+
+export interface ProposalAcceptedNotification {
+ type: NotificationType.ProposalAccepted;
+ proposalId: string;
+}
+
+export interface CoinWithdrawnNotification {
+ type: NotificationType.CoinWithdrawn;
+}
+
+export interface RefundStartedNotification {
+ type: NotificationType.RefundStarted;
+}
+
+export interface RefundQueriedNotification {
+ type: NotificationType.RefundQueried;
+}
+
+export interface ProposalDownloadedNotification {
+ type: NotificationType.ProposalDownloaded;
+ proposalId: string;
+}
+
+export interface RefundsSubmittedNotification {
+ type: NotificationType.RefundsSubmitted;
+ proposalId: string;
+}
+
+export interface PaybackStartedNotification {
+ type: NotificationType.PaybackStarted;
+}
+
+export interface PaybackFinishedNotification {
+ type: NotificationType.PaybackFinished;
+}
+
+export interface RefreshMeltedNotification {
+ type: NotificationType.RefreshMelted;
+}
+
+export interface RefreshRevealedNotification {
+ type: NotificationType.RefreshRevealed;
+}
+
+export interface RefreshStartedNotification {
+ type: NotificationType.RefreshStarted;
+}
+
+export interface RefreshRefusedNotification {
+ type: NotificationType.RefreshRefused;
+}
+
+export interface ReserveUpdatedNotification {
+ type: NotificationType.ReserveUpdated;
+}
+
+export interface ReserveConfirmedNotification {
+ type: NotificationType.ReserveConfirmed;
+}
+
+export interface WithdrawSessionCreatedNotification {
+ type: NotificationType.WithdrawSessionCreated;
+ withdrawSessionId: string;
+}
+
+export interface WithdrawSessionFinishedNotification {
+ type: NotificationType.WithdrawSessionFinished;
+ withdrawSessionId: string;
+}
+
+export interface ReserveDepletedNotification {
+ type: NotificationType.ReserveDepleted;
+ reservePub: string;
+}
+
+export interface WaitingForRetryNotification {
+ type: NotificationType.WaitingForRetry;
+ numPending: number;
+ numGivingLiveness: number;
+}
+
+export interface RefundFinishedNotification {
+ type: NotificationType.RefundFinished;
+}
+
+export interface ExchangeOperationErrorNotification {
+ type: NotificationType.ExchangeOperationError;
+}
+
+export interface RefreshOperationErrorNotification {
+ type: NotificationType.RefreshOperationError;
+}
+
+export interface RefundStatusOperationErrorNotification {
+ type: NotificationType.RefundStatusOperationError;
+}
+
+export interface RefundApplyOperationErrorNotification {
+ type: NotificationType.RefundApplyOperationError;
+}
+
+export interface PayOperationErrorNotification {
+ type: NotificationType.PayOperationError;
+}
+
+export interface ProposalOperationErrorNotification {
+ type: NotificationType.ProposalOperationError;
+}
+
+export interface TipOperationErrorNotification {
+ type: NotificationType.TipOperationError;
+}
+
+export interface WithdrawOperationErrorNotification {
+ type: NotificationType.WithdrawOperationError;
+}
+
+export interface ReserveOperationErrorNotification {
+ type: NotificationType.ReserveOperationError;
+}
+
+export interface ReserveCreatedNotification {
+ type: NotificationType.ReserveCreated;
+}
+
+export interface WildcardNotification {
+ type: NotificationType.Wildcard;
+}
+
+export type WalletNotification =
+ | WithdrawOperationErrorNotification
+ | ReserveOperationErrorNotification
+ | ExchangeOperationErrorNotification
+ | RefreshOperationErrorNotification
+ | RefundStatusOperationErrorNotification
+ | RefundApplyOperationErrorNotification
+ | ProposalOperationErrorNotification
+ | PayOperationErrorNotification
+ | TipOperationErrorNotification
+ | ProposalAcceptedNotification
+ | ProposalDownloadedNotification
+ | RefundsSubmittedNotification
+ | PaybackStartedNotification
+ | PaybackFinishedNotification
+ | RefreshMeltedNotification
+ | RefreshRevealedNotification
+ | RefreshStartedNotification
+ | RefreshRefusedNotification
+ | ReserveUpdatedNotification
+ | ReserveCreatedNotification
+ | ReserveConfirmedNotification
+ | WithdrawSessionFinishedNotification
+ | ReserveDepletedNotification
+ | WaitingForRetryNotification
+ | RefundStartedNotification
+ | RefundFinishedNotification
+ | RefundQueriedNotification
+ | WithdrawSessionCreatedNotification
+ | CoinWithdrawnNotification
+ | WildcardNotification;
diff --git a/src/types/pending.ts b/src/types/pending.ts
new file mode 100644
index 000000000..5e381d09a
--- /dev/null
+++ b/src/types/pending.ts
@@ -0,0 +1,161 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 GNUnet e.V.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Type and schema definitions for pending operations in the wallet.
+ */
+
+/**
+ * Imports.
+ */
+import { OperationError, Timestamp, Duration } from "./walletTypes";
+import { WithdrawalSource, RetryInfo } from "./dbTypes";
+
+/**
+ * Information about a pending operation.
+ */
+export type PendingOperationInfo = PendingOperationInfoCommon &
+ (
+ | PendingWithdrawOperation
+ | PendingReserveOperation
+ | PendingBugOperation
+ | PendingDirtyCoinOperation
+ | PendingExchangeUpdateOperation
+ | PendingRefreshOperation
+ | PendingTipOperation
+ | PendingProposalDownloadOperation
+ | PendingProposalChoiceOperation
+ | PendingPayOperation
+ | PendingRefundQueryOperation
+ | PendingRefundApplyOperation
+ );
+
+export interface PendingExchangeUpdateOperation {
+ type: "exchange-update";
+ stage: string;
+ reason: string;
+ exchangeBaseUrl: string;
+ lastError: OperationError | undefined;
+}
+
+export interface PendingBugOperation {
+ type: "bug";
+ message: string;
+ details: any;
+}
+
+export interface PendingReserveOperation {
+ type: "reserve";
+ retryInfo: RetryInfo | undefined;
+ stage: string;
+ timestampCreated: Timestamp;
+ reserveType: string;
+ reservePub: string;
+ bankWithdrawConfirmUrl?: string;
+}
+
+export interface PendingRefreshOperation {
+ type: "refresh";
+ lastError?: OperationError;
+ refreshSessionId: string;
+ oldCoinPub: string;
+ refreshStatus: string;
+ refreshOutputSize: number;
+}
+
+export interface PendingDirtyCoinOperation {
+ type: "dirty-coin";
+ coinPub: string;
+}
+
+export interface PendingProposalDownloadOperation {
+ type: "proposal-download";
+ merchantBaseUrl: string;
+ proposalTimestamp: Timestamp;
+ proposalId: string;
+ orderId: string;
+ lastError?: OperationError;
+ retryInfo: RetryInfo;
+}
+
+/**
+ * User must choose whether to accept or reject the merchant's
+ * proposed contract terms.
+ */
+export interface PendingProposalChoiceOperation {
+ type: "proposal-choice";
+ merchantBaseUrl: string;
+ proposalTimestamp: Timestamp;
+ proposalId: string;
+}
+
+export interface PendingTipOperation {
+ type: "tip";
+ tipId: string;
+ merchantBaseUrl: string;
+ merchantTipId: string;
+}
+
+export interface PendingPayOperation {
+ type: "pay";
+ proposalId: string;
+ isReplay: boolean;
+ retryInfo: RetryInfo,
+ lastError: OperationError | undefined;
+}
+
+export interface PendingRefundQueryOperation {
+ type: "refund-query";
+ proposalId: string;
+ retryInfo: RetryInfo,
+ lastError: OperationError | undefined;
+}
+
+export interface PendingRefundApplyOperation {
+ type: "refund-apply";
+ proposalId: string;
+ retryInfo: RetryInfo,
+ lastError: OperationError | undefined;
+ numRefundsPending: number;
+ numRefundsDone: number;
+}
+
+export interface PendingOperationInfoCommon {
+ type: string;
+ givesLifeness: boolean;
+}
+
+
+export interface PendingWithdrawOperation {
+ type: "withdraw";
+ source: WithdrawalSource;
+ withdrawSessionId: string;
+ numCoinsWithdrawn: number;
+ numCoinsTotal: number;
+}
+
+export interface PendingRefreshOperation {
+ type: "refresh";
+}
+
+export interface PendingPayOperation {
+ type: "pay";
+}
+
+export interface PendingOperationsResponse {
+ pendingOperations: PendingOperationInfo[];
+ nextRetryDelay: Duration;
+} \ No newline at end of file
diff --git a/src/types/talerTypes.ts b/src/types/talerTypes.ts
new file mode 100644
index 000000000..df89b9979
--- /dev/null
+++ b/src/types/talerTypes.ts
@@ -0,0 +1,944 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 GNUnet e.V.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Type and schema definitions and helpers for the core GNU Taler protocol.
+ *
+ * All types here should be "@Checkable".
+ *
+ * Even though the rest of the wallet uses camelCase for fields, use snake_case
+ * here, since that's the convention for the Taler JSON+HTTP API.
+ */
+
+/**
+ * Imports.
+ */
+import { Checkable } from "../util/checkable";
+
+import * as Amounts from "../util/amounts";
+
+import { timestampCheck } from "../util/helpers";
+
+/**
+ * Denomination as found in the /keys response from the exchange.
+ */
+@Checkable.Class()
+export class Denomination {
+ /**
+ * Value of one coin of the denomination.
+ */
+ @Checkable.String(Amounts.check)
+ value: string;
+
+ /**
+ * Public signing key of the denomination.
+ */
+ @Checkable.String()
+ denom_pub: string;
+
+ /**
+ * Fee for withdrawing.
+ */
+ @Checkable.String(Amounts.check)
+ fee_withdraw: string;
+
+ /**
+ * Fee for depositing.
+ */
+ @Checkable.String(Amounts.check)
+ fee_deposit: string;
+
+ /**
+ * Fee for refreshing.
+ */
+ @Checkable.String(Amounts.check)
+ fee_refresh: string;
+
+ /**
+ * Fee for refunding.
+ */
+ @Checkable.String(Amounts.check)
+ fee_refund: string;
+
+ /**
+ * Start date from which withdraw is allowed.
+ */
+ @Checkable.String(timestampCheck)
+ stamp_start: string;
+
+ /**
+ * End date for withdrawing.
+ */
+ @Checkable.String(timestampCheck)
+ stamp_expire_withdraw: string;
+
+ /**
+ * Expiration date after which the exchange can forget about
+ * the currency.
+ */
+ @Checkable.String(timestampCheck)
+ stamp_expire_legal: string;
+
+ /**
+ * Date after which the coins of this denomination can't be
+ * deposited anymore.
+ */
+ @Checkable.String(timestampCheck)
+ 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.String()
+ amount: string;
+
+ /**
+ * 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: string;
+
+ /**
+ * URL of the exchange this coin was withdrawn from.
+ */
+ exchange_url: string;
+}
+
+/**
+ * Information about an exchange as stored inside a
+ * merchant's contract terms.
+ */
+@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;
+
+ /**
+ * Hash of the merchant's wire details.
+ */
+ @Checkable.Optional(Checkable.String())
+ auto_refund?: 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.String(Amounts.check)
+ amount: string;
+
+ /**
+ * 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.String(Amounts.check)
+ max_fee: string;
+
+ /**
+ * 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(timestampCheck)
+ refund_deadline: string;
+
+ /**
+ * Deadline for the wire transfer.
+ */
+ @Checkable.String()
+ wire_transfer_deadline: string;
+
+ /**
+ * Time when the contract was generated by the merchant.
+ */
+ @Checkable.String(timestampCheck)
+ timestamp: string;
+
+ /**
+ * Order id to uniquely identify the purchase within
+ * one merchant instance.
+ */
+ @Checkable.String()
+ order_id: string;
+
+ /**
+ * Base URL of the merchant's backend.
+ */
+ @Checkable.String()
+ merchant_base_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.String())
+ max_wire_fee?: string;
+
+ /**
+ * 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;
+
+ /**
+ * Mode for /pay.
+ */
+ mode: "pay" | "abort-refund";
+}
+
+/**
+ * Refund permission in the format that the merchant gives it to us.
+ */
+@Checkable.Class()
+export class MerchantRefundPermission {
+ /**
+ * Amount to be refunded.
+ */
+ @Checkable.String(Amounts.check)
+ refund_amount: string;
+
+ /**
+ * Fee for the refund.
+ */
+ @Checkable.String(Amounts.check)
+ refund_fee: string;
+
+ /**
+ * Public key of the coin being refunded.
+ */
+ @Checkable.String()
+ coin_pub: string;
+
+ /**
+ * Refund transaction ID between merchant and exchange.
+ */
+ @Checkable.Number()
+ rtransaction_id: number;
+
+ /**
+ * Signature made by the merchant over the refund permission.
+ */
+ @Checkable.String()
+ merchant_sig: string;
+
+ /**
+ * Create a MerchantRefundPermission from untyped JSON.
+ */
+ static checked: (obj: any) => MerchantRefundPermission;
+}
+
+/**
+ * Refund request sent to the exchange.
+ */
+export interface RefundRequest {
+ /**
+ * Amount to be refunded, can be a fraction of the
+ * coin's total deposit value (including deposit fee);
+ * must be larger than the refund fee.
+ */
+ refund_amount: string;
+
+ /**
+ * Refund fee associated with the given coin.
+ * must be smaller than the refund amount.
+ */
+ refund_fee: string;
+
+ /**
+ * SHA-512 hash of the contact of the merchant with the customer.
+ */
+ h_contract_terms: string;
+
+ /**
+ * coin's public key, both ECDHE and EdDSA.
+ */
+ coin_pub: string;
+
+ /**
+ * 64-bit transaction id of the refund transaction between merchant and customer
+ */
+ rtransaction_id: number;
+
+ /**
+ * EdDSA public key of the merchant.
+ */
+ merchant_pub: string;
+
+ /**
+ * EdDSA signature of the merchant affirming the refund.
+ */
+ merchant_sig: string;
+}
+
+/**
+ * Response for a refund pickup or a /pay in abort mode.
+ */
+@Checkable.Class()
+export class MerchantRefundResponse {
+ /**
+ * Public key of the merchant
+ */
+ @Checkable.String()
+ merchant_pub: string;
+
+ /**
+ * Contract terms hash of the contract that
+ * is being refunded.
+ */
+ @Checkable.String()
+ h_contract_terms: string;
+
+ /**
+ * The signed refund permissions, to be sent to the exchange.
+ */
+ @Checkable.List(Checkable.Value(() => MerchantRefundPermission))
+ refund_permissions: MerchantRefundPermission[];
+
+ /**
+ * Create a MerchantRefundReponse from untyped JSON.
+ */
+ static checked: (obj: any) => MerchantRefundResponse;
+}
+
+/**
+ * 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 to /reserve/status
+ */
+@Checkable.Class()
+export class ReserveStatus {
+ /**
+ * Reserve signature.
+ */
+ @Checkable.String()
+ balance: string;
+
+ /**
+ * Reserve history, currently not used by the wallet.
+ */
+ @Checkable.Any()
+ history: any;
+
+ /**
+ * Create a ReserveSigSingleton from untyped JSON.
+ */
+ static checked: (obj: any) => ReserveStatus;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * Element of the payback list that the
+ * exchange gives us in /keys.
+ */
+@Checkable.Class()
+export class Payback {
+ /**
+ * The hash of the denomination public key for which the payback is offered.
+ */
+ @Checkable.String()
+ h_denom_pub: string;
+}
+
+/**
+ * Structure that the exchange gives us in /keys.
+ */
+@Checkable.Class({ extra: true })
+export class KeysJson {
+ /**
+ * List of offered denominations.
+ */
+ @Checkable.List(Checkable.Value(() => Denomination))
+ denoms: Denomination[];
+
+ /**
+ * The exchange's master public key.
+ */
+ @Checkable.String()
+ master_public_key: string;
+
+ /**
+ * The list of auditors (partially) auditing the exchange.
+ */
+ @Checkable.List(Checkable.Value(() => Auditor))
+ auditors: Auditor[];
+
+ /**
+ * Timestamp when this response was issued.
+ */
+ @Checkable.String(timestampCheck)
+ list_issue_date: string;
+
+ /**
+ * List of paybacks for compromised denominations.
+ */
+ @Checkable.Optional(Checkable.List(Checkable.Value(() => Payback)))
+ payback?: Payback[];
+
+ /**
+ * Short-lived signing keys used to sign online
+ * responses.
+ */
+ @Checkable.Any()
+ signkeys: any;
+
+ /**
+ * Protocol version.
+ */
+ @Checkable.Optional(Checkable.String())
+ version?: string;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => KeysJson;
+}
+
+/**
+ * Wire fees as anounced by the exchange.
+ */
+@Checkable.Class()
+export class WireFeesJson {
+ /**
+ * Cost of a wire transfer.
+ */
+ @Checkable.String(Amounts.check)
+ wire_fee: string;
+
+ /**
+ * Cost of clising a reserve.
+ */
+ @Checkable.String(Amounts.check)
+ closing_fee: string;
+
+ /**
+ * Signature made with the exchange's master key.
+ */
+ @Checkable.String()
+ sig: string;
+
+ /**
+ * Date from which the fee applies.
+ */
+ @Checkable.String(timestampCheck)
+ start_date: string;
+
+ /**
+ * Data after which the fee doesn't apply anymore.
+ */
+ @Checkable.String(timestampCheck)
+ end_date: string;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => WireFeesJson;
+}
+
+@Checkable.Class({ extra: true })
+export class AccountInfo {
+ @Checkable.String()
+ url: string;
+
+ @Checkable.String()
+ master_sig: string;
+}
+
+@Checkable.Class({ extra: true })
+export class ExchangeWireJson {
+ @Checkable.Map(
+ Checkable.String(),
+ Checkable.List(Checkable.Value(() => WireFeesJson)),
+ )
+ fees: { [methodName: string]: WireFeesJson[] };
+
+ @Checkable.List(Checkable.Value(() => AccountInfo))
+ accounts: AccountInfo[];
+
+ static checked: (obj: any) => ExchangeWireJson;
+}
+
+/**
+ * Wire detail, arbitrary object that must at least
+ * contain a "type" key.
+ */
+export type WireDetail = object & { type: string };
+
+/**
+ * Proposal returned from the contract URL.
+ */
+@Checkable.Class({ extra: true })
+export class Proposal {
+ /**
+ * Contract terms for the propoal.
+ */
+ @Checkable.Value(() => ContractTerms)
+ contract_terms: ContractTerms;
+
+ /**
+ * Signature over contract, made by the merchant. The public key used for signing
+ * must be contract_terms.merchant_pub.
+ */
+ @Checkable.String()
+ sig: string;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => Proposal;
+}
+
+/**
+ * Response from the internal merchant API.
+ */
+@Checkable.Class({ extra: true })
+export class CheckPaymentResponse {
+ @Checkable.Boolean()
+ paid: boolean;
+
+ @Checkable.Optional(Checkable.Boolean())
+ refunded: boolean | undefined;
+
+ @Checkable.Optional(Checkable.String())
+ refunded_amount: string | undefined;
+
+ @Checkable.Optional(Checkable.Value(() => ContractTerms))
+ contract_terms: ContractTerms | undefined;
+
+ @Checkable.Optional(Checkable.String())
+ taler_pay_uri: string | undefined;
+
+ @Checkable.Optional(Checkable.String())
+ contract_url: string | undefined;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => CheckPaymentResponse;
+}
+
+/**
+ * Response from the bank.
+ */
+@Checkable.Class({ extra: true })
+export class WithdrawOperationStatusResponse {
+ @Checkable.Boolean()
+ selection_done: boolean;
+
+ @Checkable.Boolean()
+ transfer_done: boolean;
+
+ @Checkable.String()
+ amount: string;
+
+ @Checkable.Optional(Checkable.String())
+ sender_wire?: string;
+
+ @Checkable.Optional(Checkable.String())
+ suggested_exchange?: string;
+
+ @Checkable.Optional(Checkable.String())
+ confirm_transfer_url?: string;
+
+ @Checkable.List(Checkable.String())
+ wire_types: string[];
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => WithdrawOperationStatusResponse;
+}
+
+/**
+ * Response from the merchant.
+ */
+@Checkable.Class({ extra: true })
+export class TipPickupGetResponse {
+ @Checkable.AnyObject()
+ extra: any;
+
+ @Checkable.String()
+ amount: string;
+
+ @Checkable.String()
+ amount_left: string;
+
+ @Checkable.String()
+ exchange_url: string;
+
+ @Checkable.String()
+ stamp_expire: string;
+
+ @Checkable.String()
+ stamp_created: string;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => TipPickupGetResponse;
+}
diff --git a/src/types/types-test.ts b/src/types/types-test.ts
new file mode 100644
index 000000000..a686fbe38
--- /dev/null
+++ b/src/types/types-test.ts
@@ -0,0 +1,164 @@
+/*
+ This file is part of TALER
+ (C) 2017 Inria and GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import test from "ava";
+import * as Amounts from "../util/amounts";
+import { ContractTerms } from "./talerTypes";
+
+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 === Amounts.cmp(Amounts.add(a1, a2).amount, a3));
+ t.pass();
+});
+
+test("amount addition (saturation)", t => {
+ const a1 = amt(1, 0, "EUR");
+ const res = Amounts.add(amt(Amounts.maxAmountValue, 0, "EUR"), a1);
+ t.true(res.saturated);
+ t.pass();
+});
+
+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 === Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
+ t.pass();
+});
+
+test("amount subtraction (saturation)", t => {
+ const a1 = amt(0, 0, "EUR");
+ const a2 = amt(1, 0, "EUR");
+ let res = Amounts.sub(a1, a2);
+ t.true(res.saturated);
+ res = Amounts.sub(a1, a1);
+ t.true(!res.saturated);
+ t.pass();
+});
+
+test("amount comparison", t => {
+ t.is(Amounts.cmp(amt(1, 0, "EUR"), amt(1, 0, "EUR")), 0);
+ t.is(Amounts.cmp(amt(1, 1, "EUR"), amt(1, 0, "EUR")), 1);
+ t.is(Amounts.cmp(amt(1, 1, "EUR"), amt(1, 2, "EUR")), -1);
+ t.is(Amounts.cmp(amt(1, 0, "EUR"), amt(0, 0, "EUR")), 1);
+ t.is(Amounts.cmp(amt(0, 0, "EUR"), amt(1, 0, "EUR")), -1);
+ t.is(Amounts.cmp(amt(1, 0, "EUR"), amt(0, 100000000, "EUR")), 0);
+ t.throws(() => Amounts.cmp(amt(1, 0, "FOO"), amt(1, 0, "BAR")));
+ t.pass();
+});
+
+test("amount parsing", t => {
+ t.is(
+ Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:0"), amt(0, 0, "TESTKUDOS")),
+ 0,
+ );
+ t.is(
+ Amounts.cmp(Amounts.parseOrThrow("TESTKUDOS:10"), amt(10, 0, "TESTKUDOS")),
+ 0,
+ );
+ t.is(
+ Amounts.cmp(
+ Amounts.parseOrThrow("TESTKUDOS:0.1"),
+ amt(0, 10000000, "TESTKUDOS"),
+ ),
+ 0,
+ );
+ t.is(
+ Amounts.cmp(
+ Amounts.parseOrThrow("TESTKUDOS:0.00000001"),
+ amt(0, 1, "TESTKUDOS"),
+ ),
+ 0,
+ );
+ t.is(
+ Amounts.cmp(
+ Amounts.parseOrThrow("TESTKUDOS:4503599627370496.99999999"),
+ amt(4503599627370496, 99999999, "TESTKUDOS"),
+ ),
+ 0,
+ );
+ t.throws(() => Amounts.parseOrThrow("foo:"));
+ t.throws(() => Amounts.parseOrThrow("1.0"));
+ t.throws(() => Amounts.parseOrThrow("42"));
+ t.throws(() => Amounts.parseOrThrow(":1.0"));
+ t.throws(() => Amounts.parseOrThrow(":42"));
+ t.throws(() => Amounts.parseOrThrow("EUR:.42"));
+ t.throws(() => Amounts.parseOrThrow("EUR:42."));
+ t.throws(() => Amounts.parseOrThrow("TESTKUDOS:4503599627370497.99999999"));
+ t.is(
+ Amounts.cmp(
+ Amounts.parseOrThrow("TESTKUDOS:0.99999999"),
+ amt(0, 99999999, "TESTKUDOS"),
+ ),
+ 0,
+ );
+ t.throws(() => Amounts.parseOrThrow("TESTKUDOS:0.999999991"));
+ t.pass();
+});
+
+test("amount stringification", t => {
+ t.is(Amounts.toString(amt(0, 0, "TESTKUDOS")), "TESTKUDOS:0");
+ t.is(Amounts.toString(amt(4, 94000000, "TESTKUDOS")), "TESTKUDOS:4.94");
+ t.is(Amounts.toString(amt(0, 10000000, "TESTKUDOS")), "TESTKUDOS:0.1");
+ t.is(Amounts.toString(amt(0, 1, "TESTKUDOS")), "TESTKUDOS:0.00000001");
+ t.is(Amounts.toString(amt(5, 0, "TESTKUDOS")), "TESTKUDOS:5");
+ // denormalized
+ t.is(Amounts.toString(amt(1, 100000000, "TESTKUDOS")), "TESTKUDOS:2");
+ t.pass();
+});
+
+test("contract terms validation", t => {
+ const c = {
+ H_wire: "123",
+ amount: "EUR:1.5",
+ auditors: [],
+ exchanges: [{ master_pub: "foo", url: "foo" }],
+ fulfillment_url: "foo",
+ max_fee: "EUR:1.5",
+ merchant_pub: "12345",
+ order_id: "test_order",
+ pay_deadline: "Date(12346)",
+ wire_transfer_deadline: "Date(12346)",
+ merchant_base_url: "https://example.com/pay",
+ products: [],
+ refund_deadline: "Date(12345)",
+ summary: "hello",
+ timestamp: "Date(12345)",
+ wire_method: "test",
+ };
+
+ ContractTerms.checked(c);
+
+ const c1 = JSON.parse(JSON.stringify(c));
+ c1.exchanges = [];
+
+ try {
+ ContractTerms.checked(c1);
+ } catch (e) {
+ t.pass();
+ return;
+ }
+
+ t.fail();
+});
diff --git a/src/types/walletTypes.ts b/src/types/walletTypes.ts
new file mode 100644
index 000000000..a9bf2061f
--- /dev/null
+++ b/src/types/walletTypes.ts
@@ -0,0 +1,512 @@
+/*
+ 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 "../util/checkable";
+import * as LibtoolVersion from "../util/libtoolVersion";
+
+import { AmountJson } from "../util/amounts";
+
+import {
+ CoinRecord,
+ DenominationRecord,
+ ExchangeRecord,
+ ExchangeWireInfo,
+ WithdrawalSource,
+ RetryInfo,
+} from "./dbTypes";
+import { CoinPaySig, ContractTerms, PayReq } 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;
+}
+
+/**
+ * Information about what will happen when creating a reserve.
+ *
+ * Sent to the wallet frontend to be rendered and shown to the user.
+ */
+export interface ExchangeWithdrawDetails {
+ /**
+ * Exchange that the reserve will be created at.
+ */
+ exchangeInfo: ExchangeRecord;
+
+ /**
+ * Filtered wire info to send to the bank.
+ */
+ exchangeWireAccounts: string[];
+
+ /**
+ * Selected denominations for withdraw.
+ */
+ selectedDenoms: DenominationRecord[];
+
+ /**
+ * Fees for withdraw.
+ */
+ withdrawFee: AmountJson;
+
+ /**
+ * Remaining balance that is too small to be withdrawn.
+ */
+ overhead: AmountJson;
+
+ /**
+ * Wire fees from the exchange.
+ */
+ wireFees: ExchangeWireInfo;
+
+ /**
+ * Does the wallet know about an auditor for
+ * the exchange that the reserve.
+ */
+ isAudited: boolean;
+
+ /**
+ * Did the user already accept the current terms of service for the exchange?
+ */
+ termsOfServiceAccepted: boolean;
+
+ /**
+ * The exchange is trusted directly.
+ */
+ isTrusted: boolean;
+
+ /**
+ * The earliest deposit expiration of the selected coins.
+ */
+ earliestDepositExpiration: Timestamp;
+
+ /**
+ * Number of currently offered denominations.
+ */
+ numOfferedDenoms: number;
+
+ /**
+ * Public keys of trusted auditors for the currency we're withdrawing.
+ */
+ trustedAuditorPubs: string[];
+
+ /**
+ * Result of checking the wallet's version
+ * against the exchange's version.
+ *
+ * Older exchanges don't return version information.
+ */
+ versionMatch: LibtoolVersion.VersionMatchResult | undefined;
+
+ /**
+ * Libtool-style version string for the exchange or "unknown"
+ * for older exchanges.
+ */
+ exchangeVersion: string;
+
+ /**
+ * Libtool-style version string for the wallet.
+ */
+ walletVersion: string;
+}
+
+export interface WithdrawDetails {
+ bankWithdrawDetails: BankWithdrawDetails;
+ exchangeWithdrawDetails: ExchangeWithdrawDetails | undefined;
+}
+
+/**
+ * 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;
+
+ pendingIncomingWithdraw: AmountJson;
+ pendingIncomingRefresh: AmountJson;
+ pendingIncomingDirty: 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[];
+}
+
+/**
+ * For terseness.
+ */
+export function mkAmount(
+ value: number,
+ fraction: number,
+ currency: string,
+): AmountJson {
+ return { value, fraction, currency };
+}
+
+/**
+ * Result for confirmPay
+ */
+export interface ConfirmPayResult {
+ nextUrl: string;
+}
+
+/**
+ * Information about all sender wire details known to the wallet,
+ * as well as exchanges that accept these wire types.
+ */
+export interface SenderWireInfos {
+ /**
+ * Mapping from exchange base url to list of accepted
+ * wire types.
+ */
+ exchangeWireTypes: { [exchangeBaseUrl: string]: string[] };
+
+ /**
+ * Sender wire information stored in the wallet.
+ */
+ senderWires: string[];
+}
+
+/**
+ * Request to 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;
+
+ /**
+ * Payto URI that identifies the exchange's account that the funds
+ * for this reserve go into.
+ */
+ @Checkable.String()
+ exchangeWire: string;
+
+ /**
+ * Wire details (as a payto URI) for the bank account that sent the funds to
+ * the exchange.
+ */
+ @Checkable.Optional(Checkable.String())
+ senderWire?: string;
+
+ /**
+ * URL to fetch the withdraw status from the bank.
+ */
+ @Checkable.Optional(Checkable.String())
+ bankWithdrawStatusUrl?: string;
+
+ /**
+ * 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;
+ /**
+ * Total amount, including wire fees payed by the customer.
+ */
+ totalAmount: 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 {
+ accepted: boolean;
+ amount: AmountJson;
+ amountLeft: AmountJson;
+ nextUrl: string;
+ exchangeUrl: string;
+ tipId: string;
+ merchantTipId: string;
+ merchantOrigin: string;
+ expirationTimestamp: number;
+ timestamp: number;
+ totalFees: AmountJson;
+}
+
+export interface BenchmarkResult {
+ time: { [s: string]: number };
+ repetitions: number;
+}
+
+/**
+ * Cached next URL for a particular session id.
+ */
+export interface NextUrlResult {
+ nextUrl: string;
+ lastSessionId: string | undefined;
+}
+
+export type PreparePayResult =
+ | PreparePayResultError
+ | PreparePayResultInsufficientBalance
+ | PreparePayResultPaid
+ | PreparePayResultPaymentPossible;
+
+export interface PreparePayResultPaymentPossible {
+ status: "payment-possible";
+ proposalId: string;
+ contractTerms: ContractTerms;
+ totalFees: AmountJson;
+}
+
+export interface PreparePayResultInsufficientBalance {
+ status: "insufficient-balance";
+ proposalId: string;
+ contractTerms: ContractTerms;
+}
+
+export interface PreparePayResultError {
+ status: "error";
+ error: string;
+}
+
+export interface PreparePayResultPaid {
+ status: "paid";
+ contractTerms: ContractTerms;
+ nextUrl: string;
+}
+
+export interface BankWithdrawDetails {
+ selectionDone: boolean;
+ transferDone: boolean;
+ amount: AmountJson;
+ senderWire?: string;
+ suggestedExchange?: string;
+ confirmTransferUrl?: string;
+ wireTypes: string[];
+ extractedStatusUrl: string;
+}
+
+export interface AcceptWithdrawalResponse {
+ reservePub: string;
+ confirmTransferUrl?: string;
+}
+
+/**
+ * Details about a purchase, including refund status.
+ */
+export interface PurchaseDetails {
+ contractTerms: ContractTerms;
+ hasRefund: boolean;
+ totalRefundAmount: AmountJson;
+ totalRefundAndRefreshFees: AmountJson;
+}
+
+export interface WalletDiagnostics {
+ walletManifestVersion: string;
+ walletManifestDisplayVersion: string;
+ errors: string[];
+ firefoxIdbProblem: boolean;
+ dbOutdated: boolean;
+}
+
+export interface OperationError {
+ type: string;
+ message: string;
+ details: any;
+}
+
+@Checkable.Class()
+export class Timestamp {
+ /**
+ * Timestamp in milliseconds.
+ */
+ @Checkable.Number()
+ readonly t_ms: number;
+
+ static checked: (obj: any) => Timestamp;
+}
+
+export interface Duration {
+ /**
+ * Duration in milliseconds.
+ */
+ readonly d_ms: number;
+}
+
+export function getTimestampNow(): Timestamp {
+ return {
+ t_ms: new Date().getTime(),
+ };
+}
+
+export interface PlanchetCreationResult {
+ coinPub: string;
+ coinPriv: string;
+ reservePub: string;
+ denomPubHash: string;
+ denomPub: string;
+ blindingKey: string;
+ withdrawSig: string;
+ coinEv: string;
+ coinValue: AmountJson;
+}
+
+export interface PlanchetCreationRequest {
+ value: AmountJson;
+ feeWithdraw: AmountJson;
+ denomPub: string;
+ reservePub: string;
+ reservePriv: string;
+}