From 74433c3e05734aa1194049fcbcaa92c70ce61c74 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 12 Dec 2019 20:53:15 +0100 Subject: refactor: re-structure type definitions --- src/types/dbTypes.ts | 1388 ++++++++++++++++++++++++++++++++++++++++++++ src/types/history.ts | 58 ++ src/types/notifications.ts | 213 +++++++ src/types/pending.ts | 161 +++++ src/types/talerTypes.ts | 944 ++++++++++++++++++++++++++++++ src/types/types-test.ts | 164 ++++++ src/types/walletTypes.ts | 512 ++++++++++++++++ 7 files changed, 3440 insertions(+) create mode 100644 src/types/dbTypes.ts create mode 100644 src/types/history.ts create mode 100644 src/types/notifications.ts create mode 100644 src/types/pending.ts create mode 100644 src/types/talerTypes.ts create mode 100644 src/types/types-test.ts create mode 100644 src/types/walletTypes.ts (limited to 'src/types') 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 + */ + +/** + * 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 { + constructor() { + super("exchanges", { keyPath: "baseUrl" }); + } + } + + class CoinsStore extends Store { + constructor() { + super("coins", { keyPath: "coinPub" }); + } + + exchangeBaseUrlIndex = new Index( + this, + "exchangeBaseUrl", + "exchangeBaseUrl", + ); + denomPubIndex = new Index( + this, + "denomPubIndex", + "denomPub", + ); + byWithdrawalWithIdx = new Index( + this, + "planchetsByWithdrawalWithIdxIndex", + ["withdrawSessionId", "coinIndex"], + ); + } + + class ProposalsStore extends Store { + constructor() { + super("proposals", { keyPath: "proposalId" }); + } + urlAndOrderIdIndex = new Index(this, "urlIndex", [ + "merchantBaseUrl", + "orderId", + ]); + } + + class PurchasesStore extends Store { + constructor() { + super("purchases", { keyPath: "proposalId" }); + } + + fulfillmentUrlIndex = new Index( + this, + "fulfillmentUrlIndex", + "contractTerms.fulfillment_url", + ); + orderIdIndex = new Index(this, "orderIdIndex", [ + "contractTerms.merchant_base_url", + "contractTerms.order_id", + ]); + } + + class DenominationsStore extends Store { + constructor() { + // cast needed because of bug in type annotations + super("denominations", { + keyPath: (["exchangeBaseUrl", "denomPub"] as any) as IDBKeyPath, + }); + } + + denomPubHashIndex = new Index( + this, + "denomPubHashIndex", + "denomPubHash", + ); + exchangeBaseUrlIndex = new Index( + this, + "exchangeBaseUrlIndex", + "exchangeBaseUrl", + ); + denomPubIndex = new Index( + this, + "denomPubIndex", + "denomPub", + ); + } + + class CurrenciesStore extends Store { + constructor() { + super("currencies", { keyPath: "name" }); + } + } + + class ConfigStore extends Store { + constructor() { + super("config", { keyPath: "key" }); + } + } + + class ReservesStore extends Store { + constructor() { + super("reserves", { keyPath: "reservePub" }); + } + } + + class TipsStore extends Store { + constructor() { + super("tips", { keyPath: "tipId" }); + } + } + + class SenderWiresStore extends Store { + constructor() { + super("senderWires", { keyPath: "paytoUri" }); + } + } + + class WithdrawalSessionsStore extends Store { + constructor() { + super("withdrawals", { keyPath: "withdrawSessionId" }); + } + } + + class BankWithdrawUrisStore extends Store { + constructor() { + super("bankWithdrawUris", { keyPath: "talerWithdrawUri" }); + } + } + + export const coins = new CoinsStore(); + export const coinsReturns = new Store("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("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 + */ + +/** + * 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 + */ + +/** + * 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 + */ + +/** + * 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 + */ + +/** + * 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 + */ + +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 + */ + +/** + * 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; +} -- cgit v1.2.3