commit 86f045f6730bea2a02398326602efac9a81b1687 parent e37fa59b6d9606c09ca447bc3a3fb73d61bd6de1 Author: Florian Dold <florian@dold.me> Date: Mon, 8 Jul 2024 14:40:25 +0200 util: towards API type decl reorg Diffstat:
45 files changed, 9110 insertions(+), 9017 deletions(-)
diff --git a/packages/taler-harness/src/bench2.ts b/packages/taler-harness/src/bench2.ts @@ -27,7 +27,6 @@ import { } from "@gnu-taler/taler-util"; import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { - applyRunConfigDefaults, CryptoDispatcher, SynchronousCryptoWorkerFactoryPlain, } from "@gnu-taler/taler-wallet-core"; @@ -67,7 +66,7 @@ export async function runBench2(configJson: any): Promise<void> { const reserveAmount = (numDeposits + 1) * 10; - const defaultConfig = applyRunConfigDefaults(); + const denomselAllowLate = false; for (let i = 0; i < numIter; i++) { const exchangeInfo = await downloadExchangeInfo(benchConf.exchange, http); @@ -91,7 +90,7 @@ export async function runBench2(configJson: any): Promise<void> { console.log("reserve found"); const d1 = findDenomOrThrow(exchangeInfo, `${curr}:8` as AmountString, { - denomselAllowLate: defaultConfig.testing.denomselAllowLate, + denomselAllowLate, }); for (let j = 0; j < numDeposits; j++) { @@ -120,10 +119,10 @@ export async function runBench2(configJson: any): Promise<void> { const refreshDenoms = [ findDenomOrThrow(exchangeInfo, `${curr}:1` as AmountString, { - denomselAllowLate: defaultConfig.testing.denomselAllowLate, + denomselAllowLate, }), findDenomOrThrow(exchangeInfo, `${curr}:1` as AmountString, { - denomselAllowLate: defaultConfig.testing.denomselAllowLate, + denomselAllowLate, }), ]; diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts @@ -33,9 +33,8 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import * as http from "node:http"; import { defaultCoinConfig } from "../harness/denomStructures.js"; import { - BankService, + BankService, ExchangeService, - FakebankService, GlobalTestState, HarnessExchangeBankAccount, MerchantService, @@ -104,7 +103,7 @@ async function runTestfakeConversionService(): Promise<TestfakeConversionService cashout_rounding_mode: "zero", cashout_tiny_amount: "A:1" as AmountString, }, - } satisfies TalerBankConversionApi.IntegrationConfig), + } satisfies TalerBankConversionApi.TalerConversionInfoConfig), ); } else if (path === "/cashin-rate") { res.writeHead(200, { "Content-Type": "application/json" }); @@ -219,10 +218,9 @@ export async function runWithdrawalConversionTest(t: GlobalTestState) { ), }); - const { walletClient } = await createWalletDaemonWithClient( - t, - { name: "wallet" }, - ); + const { walletClient } = await createWalletDaemonWithClient(t, { + name: "wallet", + }); await runTestfakeConversionService(); diff --git a/packages/taler-util/src/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts @@ -41,7 +41,7 @@ import { opSuccessFromHttp, opUnknownFailure, } from "./operation.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; import { TalerProtocolDuration } from "./time.js"; const logger = new Logger("MerchantApiClient.ts"); diff --git a/packages/taler-util/src/ReserveStatus.ts b/packages/taler-util/src/ReserveStatus.ts @@ -23,7 +23,7 @@ */ import { codecForAmountString } from "./amounts.js"; import { Codec, buildCodecForObject } from "./codec.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; /** * Status of a reserve. diff --git a/packages/taler-util/src/ReserveTransaction.ts b/packages/taler-util/src/ReserveTransaction.ts @@ -38,7 +38,7 @@ import { EddsaSignatureString, EddsaPublicKeyString, CoinPublicKeyString, -} from "./taler-types.js"; +} from "./types-taler-common.js"; import { AbsoluteTime, codecForTimestamp, diff --git a/packages/taler-util/src/amounts.test.ts b/packages/taler-util/src/amounts.test.ts @@ -17,7 +17,7 @@ import test from "ava"; import { Amounts, AmountJson, amountMaxValue } from "./amounts.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; const jAmt = ( value: number, diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts @@ -31,7 +31,7 @@ import { renderContext, } from "./codec.js"; import { CurrencySpecification } from "./index.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; /** * Number of fractional units that one value unit represents. diff --git a/packages/taler-util/src/backup-types.ts b/packages/taler-util/src/backup-types.ts @@ -1,42 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -import { AmountString } from "./taler-types.js"; - -export interface BackupRecovery { - walletRootPriv: string; - providers: { - name: string; - url: string; - }[]; -} - -export class BackupBackupProviderTerms { - /** - * Last known supported protocol version. - */ - supported_protocol_version: string; - - /** - * Last known annual fee. - */ - annual_fee: AmountString; - - /** - * Last known storage limit. - */ - storage_limit_in_megabytes: number; -} diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts @@ -22,6 +22,7 @@ * Imports. */ import { + AccountData, AmountString, base64FromArrayBuffer, buildCodecForObject, @@ -36,6 +37,7 @@ import { opEmptySuccess, opKnownHttpFailure, opUnknownFailure, + RegisterAccountRequest, stringToBytes, TalerError, TalerErrorCode, @@ -46,7 +48,6 @@ import { expectSuccessResponseOrThrow, HttpRequestLibrary, readSuccessResponseJsonOrThrow, - readSuccessResponseTextOrThrow, readTalerErrorResponse, } from "@gnu-taler/taler-util/http"; @@ -152,83 +153,11 @@ export class WireGatewayApiClient { } } -export interface ChallengeContactData { - // E-Mail address - email?: string; - - // Phone number. - phone?: string; -} - export interface AccountBalance { amount: AmountString; credit_debit_indicator: "credit" | "debit"; } -export interface RegisterAccountRequest { - // Username - username: string; - - // Password. - password: string; - - // Legal name of the account owner - name: string; - - // Defaults to false. - is_public?: boolean; - - // Is this a taler exchange account? - // If true: - // - incoming transactions to the account that do not - // have a valid reserve public key are automatically - // - the account provides the taler-wire-gateway-api endpoints - // Defaults to false. - is_taler_exchange?: boolean; - - // Addresses where to send the TAN for transactions. - // Currently only used for cashouts. - // If missing, cashouts will fail. - // In the future, might be used for other transactions - // as well. - challenge_contact_data?: ChallengeContactData; - - // 'payto' address pointing a bank account - // external to the libeufin-bank. - // Payments will be sent to this bank account - // when the user wants to convert the local currency - // back to fiat currency outside libeufin-bank. - cashout_payto_uri?: string; - - // Internal payto URI of this bank account. - // Used mostly for testing. - payto_uri?: string; -} - -export interface AccountData { - // Legal name of the account owner. - name: string; - - // Available balance on the account. - balance: AccountBalance; - - // payto://-URI of the account. - payto_uri: string; - - // Number indicating the max debit allowed for the requesting user. - debit_threshold: AmountString; - - contact_data?: ChallengeContactData; - - // 'payto' address pointing the bank account - // where to send cashouts. This field is optional - // because not all the accounts are required to participate - // in the merchants' circuit. One example is the exchange: - // that never cashouts. Registering these accounts can - // be done via the access API. - cashout_payto_uri?: string; -} - export interface ConfirmWithdrawalArgs { withdrawalOperationId: string; } diff --git a/packages/taler-util/src/clk.ts b/packages/taler-util/src/clk.ts @@ -23,7 +23,7 @@ import { readlinePrompt, pathBasename, } from "#compat-impl"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; export namespace clk { class Converter<T> {} diff --git a/packages/taler-util/src/http-client/bank-conversion.ts b/packages/taler-util/src/http-client/bank-conversion.ts @@ -31,14 +31,13 @@ import { opUnknownFailure, } from "../operation.js"; import { TalerErrorCode } from "../taler-error-codes.js"; -import { codecForTalerErrorDetail } from "../wallet-types.js"; import { - AccessToken, - TalerBankConversionApi, codecForCashinConversionResponse, codecForCashoutConversionResponse, codecForConversionBankConfig, -} from "./types.js"; +} from "../types-taler-bank-conversion.js"; +import { codecForTalerErrorDetail } from "../types-taler-wallet.js"; +import { AccessToken, TalerBankConversionApi } from "./types.js"; import { CacheEvictor, makeBearerTokenAuthHeader, diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts @@ -23,8 +23,6 @@ import { OperationFail, OperationOk, TalerErrorCode, - codecForChallenge, - codecForTanTransmission, opKnownAlternativeFailure, opKnownHttpFailure, opKnownTalerFailure, @@ -42,12 +40,8 @@ import { opSuccessFromHttp, opUnknownFailure, } from "../operation.js"; +import { WithdrawalOperationStatus } from "../types-taler-bank-integration.js"; import { - AccessToken, - PaginationParams, - TalerCorebankApi, - UserAndToken, - WithdrawalOperationStatus, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountTransactionInfo, @@ -55,6 +49,7 @@ import { codecForCashoutPending, codecForCashoutStatusResponse, codecForCashouts, + codecForChallenge, codecForCoreBankConfig, codecForCreateTransactionResponse, codecForGlobalCashouts, @@ -62,7 +57,14 @@ import { codecForMonitorResponse, codecForPublicAccountsResponse, codecForRegisterAccountResponse, + codecForTanTransmission, codecForWithdrawalPublicInfo, +} from "../types-taler-corebank.js"; +import { + AccessToken, + PaginationParams, + TalerCorebankApi, + UserAndToken, } from "./types.js"; import { CacheEvictor, @@ -91,6 +93,7 @@ export enum TalerCoreBankCacheEviction { CREATE_WITHDRAWAL, CREATE_CASHOUT, } + /** * Protocol version spoken with the core bank. * diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts @@ -29,15 +29,14 @@ import { opUnknownFailure, } from "../operation.js"; import { TalerErrorCode } from "../taler-error-codes.js"; -import { codecForTalerErrorDetail } from "../wallet-types.js"; import { - LongPollParams, - TalerBankIntegrationApi, WithdrawalOperationStatus, codecForBankWithdrawalOperationPostResponse, codecForBankWithdrawalOperationStatus, - codecForIntegrationBankConfig, -} from "./types.js"; +} from "../types-taler-bank-integration.js"; +import { codecForIntegrationBankConfig } from "../types-taler-corebank.js"; +import { codecForTalerErrorDetail } from "../types-taler-wallet.js"; +import { LongPollParams, TalerBankIntegrationApi } from "./types.js"; import { addLongPollingParam } from "./utils.js"; export type TalerBankIntegrationResultByMethod< @@ -84,7 +83,7 @@ export class TalerBankIntegrationHttpClient { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForIntegrationBankConfig()); default: - logger.warn(`config request failed, status ${resp.status}`) + logger.warn(`config request failed, status ${resp.status}`); return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts @@ -120,7 +120,10 @@ export class TalerRevenueHttpClient { return opSuccessFromHttp(resp, codecForRevenueIncomingHistory()); // FIXME: missing in docs case HttpStatusCode.NoContent: - return opFixedSuccess({incoming_transactions: [], credit_account: "" }); + return opFixedSuccess({ + incoming_transactions: [], + credit_account: "", + }); case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: diff --git a/packages/taler-util/src/http-client/bank-wire.ts b/packages/taler-util/src/http-client/bank-wire.ts @@ -14,7 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { HttpRequestLibrary, makeBasicAuthHeader, readTalerErrorResponse } from "../http-common.js"; +import { + HttpRequestLibrary, + makeBasicAuthHeader, + readTalerErrorResponse, +} from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; import { @@ -26,13 +30,15 @@ import { opUnknownFailure, } from "../operation.js"; import { - LongPollParams, - PaginationParams, - TalerWireGatewayApi, codecForAddIncomingResponse, codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse, +} from "../types-taler-wire-gateway.js"; +import { + LongPollParams, + PaginationParams, + TalerWireGatewayApi, } from "./types.js"; import { addLongPollingParam, addPaginationParams } from "./utils.js"; diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts @@ -11,7 +11,6 @@ import { opUnknownFailure, } from "../operation.js"; import { - AccessToken, codecForChallengeInvalidPinResponse, codecForChallengeResponse, codecForChallengeSetupResponse, @@ -20,7 +19,8 @@ import { codecForChallengerAuthResponse, codecForChallengerInfoResponse, codecForChallengerTermsOfServiceResponse, -} from "./types.js"; +} from "../types-taler-challenger.js"; +import { AccessToken } from "./types.js"; import { CacheEvictor, makeBearerTokenAuthHeader, diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts @@ -46,7 +46,6 @@ export enum TalerExchangeCacheEviction { CREATE_DESCISION, } - /** */ export class TalerExchangeHttpClient { diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -27,7 +27,6 @@ import { codecForAccountKycRedirects, codecForAccountsSummaryResponse, codecForBankAccountDetail, - codecForBankAccountEntry, codecForClaimResponse, codecForInstancesResponse, codecForInventorySummaryResponse, diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts @@ -16,22 +16,43 @@ import { codecOptionalDefault, } from "../codec.js"; import { PaytoString, codecForPaytoString } from "../payto.js"; -import { - AmountString, - ExchangeWireAccount, - InternationalizedString, - codecForExchangeWireAccount, - codecForInternationalizedString, - codecForLocation, -} from "../taler-types.js"; import { TalerUriString, codecForTalerUriString } from "../taleruri.js"; import { AbsoluteTime, TalerProtocolDuration, - TalerProtocolTimestamp, codecForDuration, codecForTimestamp, } from "../time.js"; +import { WithdrawalOperationStatus } from "../types-taler-bank-integration.js"; +import { + AmlOfficerPublicKeyP, + AmountString, + ClaimToken, + CoinPublicKey, + Cs25519Point, + CurrencySpecification, + DecimalNumber, + EddsaPublicKey, + HashCode, + ImageDataUrl, + Integer, + PaytoHash, + RelativeTime, + RsaPublicKey, + SafeUint64, + ShortHashCode, + Timestamp, + WireTransferIdentifierRawP, + codecForCurrencySpecificiation, + codecForDecimalNumber, + codecForInternationalizedString, + codecForURLString, +} from "../types-taler-common.js"; +import { + ExchangeWireAccount, + codecForExchangeWireAccount, +} from "../types-taler-exchange.js"; +import { codecForLocation } from "../types-taler-merchant.js"; export type UserAndPassword = { username: string; @@ -83,104 +104,6 @@ export type LongPollParams = { /// HASH /// -// 64-byte hash code. -type HashCode = string; - -type PaytoHash = string; - -type AmlOfficerPublicKeyP = string; - -// 32-byte hash code. -type ShortHashCode = string; - -// 16-byte salt. -type WireSalt = string; - -type SHA256HashCode = ShortHashCode; - -type SHA512HashCode = HashCode; - -// 32-byte nonce value, must only be used once. -type CSNonce = string; - -// 32-byte nonce value, must only be used once. -type RefreshMasterSeed = string; - -// 32-byte value representing a point on Curve25519. -type Cs25519Point = string; - -// 32-byte value representing a scalar multiplier -// for scalar operations on points on Curve25519. -type Cs25519Scalar = string; - -/// -/// KEYS -/// - -// 16-byte access token used to authorize access. -type ClaimToken = string; - -// EdDSA and ECDHE public keys always point on Curve25519 -// and represented using the standard 256 bits Ed25519 compact format, -// converted to Crockford Base32. -type EddsaPublicKey = string; - -// EdDSA and ECDHE public keys always point on Curve25519 -// and represented using the standard 256 bits Ed25519 compact format, -// converted to Crockford Base32. -type EddsaPrivateKey = string; - -// Edx25519 public keys are points on Curve25519 and represented using the -// standard 256 bits Ed25519 compact format converted to Crockford -// Base32. -type Edx25519PublicKey = string; - -// Edx25519 private keys are always points on Curve25519 -// and represented using the standard 256 bits Ed25519 compact format, -// converted to Crockford Base32. -type Edx25519PrivateKey = string; - -// EdDSA and ECDHE public keys always point on Curve25519 -// and represented using the standard 256 bits Ed25519 compact format, -// converted to Crockford Base32. -type EcdhePublicKey = string; - -// Point on Curve25519 represented using the standard 256 bits Ed25519 compact format, -// converted to Crockford Base32. -type CsRPublic = string; - -// EdDSA and ECDHE public keys always point on Curve25519 -// and represented using the standard 256 bits Ed25519 compact format, -// converted to Crockford Base32. -type EcdhePrivateKey = string; - -type CoinPublicKey = EddsaPublicKey; - -// RSA public key converted to Crockford Base32. -type RsaPublicKey = string; - -type Integer = number; - -type WireTransferIdentifierRawP = string; -// Subset of numbers: Integers in the -// inclusive range 0 .. (2^53 - 1). -type SafeUint64 = number; - -// The string must be a data URL according to RFC 2397 -// with explicit mediatype and base64 parameters. -// -// data:<mediatype>;base64,<data> -// -// Supported mediatypes are image/jpeg and image/png. -// Invalid strings will be rejected by the wallet. -type ImageDataUrl = string; - -type WadId = string; - -type Timestamp = TalerProtocolTimestamp; - -type RelativeTime = TalerProtocolDuration; - export interface LoginToken { token: AccessToken; expiration: Timestamp; @@ -279,28 +202,6 @@ export namespace TalerAuthentication { } } -// DD51 https://docs.taler.net/design-documents/051-fractional-digits.html -export interface CurrencySpecification { - // Name of the currency. - name: string; - - // how many digits the user may enter after the decimal_separator - num_fractional_input_digits: Integer; - - // Number of fractional digits to render in normal font and size. - num_fractional_normal_digits: Integer; - - // Number of fractional digits to render always, if needed by - // padding with zeros. - num_fractional_trailing_zero_digits: Integer; - - // map of powers of 10 to alternative currency names / symbols, must - // always have an entry under "0" that defines the base name, - // e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC". - // Communicates the currency symbol to be used. - alt_unit_names: { [log10: string]: string }; -} - //FIXME: implement this codec export const codecForAccessToken = codecForString as () => Codec<AccessToken>; export const codecForTokenSuccessResponse = @@ -317,51 +218,6 @@ export const codecForTokenSuccessResponseMerchant = .property("expiration", codecForTimestamp) .build("TalerAuthentication.TokenSuccessResponseMerchant"); -export const codecForCurrencySpecificiation = - (): Codec<CurrencySpecification> => - buildCodecForObject<CurrencySpecification>() - .property("name", codecForString()) - .property("num_fractional_input_digits", codecForNumber()) - .property("num_fractional_normal_digits", codecForNumber()) - .property("num_fractional_trailing_zero_digits", codecForNumber()) - .property("alt_unit_names", codecForMap(codecForString())) - .build("CurrencySpecification"); - -export const codecForIntegrationBankConfig = - (): Codec<TalerCorebankApi.IntegrationConfig> => - buildCodecForObject<TalerCorebankApi.IntegrationConfig>() - .property("name", codecForConstString("taler-bank-integration")) - .property("version", codecForString()) - .property("currency", codecForString()) - .property("currency_specification", codecForCurrencySpecificiation()) - .build("TalerCorebankApi.IntegrationConfig"); - -export const codecForCoreBankConfig = (): Codec<TalerCorebankApi.Config> => - buildCodecForObject<TalerCorebankApi.Config>() - .property("name", codecForConstString("libeufin-bank")) - .property("version", codecForString()) - .property("bank_name", codecForString()) - .property("base_url", codecOptional(codecForString())) - .property("allow_conversion", codecForBoolean()) - .property("allow_registrations", codecForBoolean()) - .property("allow_deletions", codecForBoolean()) - .property("allow_edit_name", codecForBoolean()) - .property("allow_edit_cashout_payto_uri", codecForBoolean()) - .property("default_debit_threshold", codecForAmountString()) - .property("currency", codecForString()) - .property("currency_specification", codecForCurrencySpecificiation()) - .property( - "supported_tan_channels", - codecForList( - codecForEither( - codecForConstString(TalerCorebankApi.TanChannel.SMS), - codecForConstString(TalerCorebankApi.TanChannel.EMAIL), - ), - ), - ) - .property("wire_type", codecOptionalDefault(codecForString(), "iban")) - .property("wire_transfer_fees", codecOptional(codecForAmountString())) - .build("TalerCorebankApi.Config"); //FIXME: implement this codec export const codecForURN = codecForString; @@ -410,7 +266,7 @@ export const codecForStatusPaid = (): Codec<TalerMerchantApi.StatusPaid> => export const codecForStatusGoto = (): Codec<TalerMerchantApi.StatusGotoResponse> => buildCodecForObject<TalerMerchantApi.StatusGotoResponse>() - .property("public_reorder_url", codecForURL()) + .property("public_reorder_url", codecForURLString()) .property("type", codecForConstString("goto")) .build("TalerMerchantApi.StatusGotoResponse"); @@ -548,16 +404,16 @@ export const codecForAccountKycRedirects = export const codecForMerchantAccountKycRedirect = (): Codec<TalerMerchantApi.MerchantAccountKycRedirect> => buildCodecForObject<TalerMerchantApi.MerchantAccountKycRedirect>() - .property("kyc_url", codecForURL()) + .property("kyc_url", codecForURLString()) .property("aml_status", codecForNumber()) - .property("exchange_url", codecForURL()) + .property("exchange_url", codecForURLString()) .property("payto_uri", codecForPaytoString()) .build("TalerMerchantApi.MerchantAccountKycRedirect"); export const codecForExchangeKycTimeout = (): Codec<TalerMerchantApi.ExchangeKycTimeout> => buildCodecForObject<TalerMerchantApi.ExchangeKycTimeout>() - .property("exchange_url", codecForURL()) + .property("exchange_url", codecForURLString()) .property("exchange_code", codecForNumber()) .property("exchange_http_status", codecForNumber()) .build("TalerMerchantApi.ExchangeKycTimeout"); @@ -589,7 +445,7 @@ export const codecForBankAccountDetail = .property("payto_uri", codecForPaytoString()) .property("h_wire", codecForString()) .property("salt", codecForString()) - .property("credit_facade_url", codecOptional(codecForURL())) + .property("credit_facade_url", codecOptional(codecForURLString())) .property("active", codecOptional(codecForBoolean())) .build("TalerMerchantApi.BankAccountEntry"); @@ -776,7 +632,7 @@ export const codecForCheckPaymentPaidResponse = .property("wire_reports", codecForList(codecForTransactionWireReport())) .property("wire_details", codecForList(codecForTransactionWireTransfer())) .property("refund_details", codecForList(codecForRefundDetails())) - .property("order_status_url", codecForURL()) + .property("order_status_url", codecForURLString()) .build("TalerMerchantApi.CheckPaymentPaidResponse"); export const codecForCheckPaymentUnpaidResponse = @@ -820,7 +676,7 @@ export const codecForRefundDetails = export const codecForTransactionWireTransfer = (): Codec<TalerMerchantApi.TransactionWireTransfer> => buildCodecForObject<TalerMerchantApi.TransactionWireTransfer>() - .property("exchange_url", codecForURL()) + .property("exchange_url", codecForURLString()) .property("wtid", codecForString()) .property("execution_time", codecForTimestamp) .property("amount", codecForAmountString()) @@ -855,7 +711,7 @@ export const codecForTransferDetails = .property("credit_amount", codecForAmountString()) .property("wtid", codecForString()) .property("payto_uri", codecForPaytoString()) - .property("exchange_url", codecForURL()) + .property("exchange_url", codecForURLString()) .property("transfer_serial_id", codecForNumber()) .property("execution_time", codecOptional(codecForTimestamp)) .property("verified", codecOptional(codecForBoolean())) @@ -997,1743 +853,226 @@ export const codecForTokenFamilySummary = .property("kind", codecForTokenFamilyKind()) .build("TalerMerchantApi.TokenFamilySummary"); -export const codecForInstancesResponse = - (): Codec<TalerMerchantApi.InstancesResponse> => - buildCodecForObject<TalerMerchantApi.InstancesResponse>() - .property("instances", codecForList(codecForInstance())) - .build("TalerMerchantApi.InstancesResponse"); - -export const codecForInstance = (): Codec<TalerMerchantApi.Instance> => - buildCodecForObject<TalerMerchantApi.Instance>() - .property("name", codecForString()) - .property("user_type", codecForString()) - .property("website", codecOptional(codecForString())) - .property("logo", codecOptional(codecForString())) - .property("id", codecForString()) - .property("merchant_pub", codecForString()) - .property("payment_targets", codecForList(codecForString())) - .property("deleted", codecForBoolean()) - .build("TalerMerchantApi.Instance"); - -export const codecForExchangeConfig = - (): Codec<TalerExchangeApi.ExchangeVersionResponse> => - buildCodecForObject<TalerExchangeApi.ExchangeVersionResponse>() - .property("version", codecForString()) - .property("name", codecForConstString("taler-exchange")) - .property("implementation", codecOptional(codecForURN())) - .property("currency", codecForString()) - .property("currency_specification", codecForCurrencySpecificiation()) - .property("supported_kyc_requirements", codecForList(codecForString())) - .build("TalerExchangeApi.ExchangeVersionResponse"); - -export const codecForExchangeKeys = - (): Codec<TalerExchangeApi.ExchangeKeysResponse> => - buildCodecForObject<TalerExchangeApi.ExchangeKeysResponse>() - .property("version", codecForString()) - .property("base_url", codecForString()) - .property("currency", codecForString()) - .build("TalerExchangeApi.ExchangeKeysResponse"); - -const codecForBalance = (): Codec<TalerCorebankApi.Balance> => - buildCodecForObject<TalerCorebankApi.Balance>() - .property("amount", codecForAmountString()) - .property( - "credit_debit_indicator", - codecForEither( - codecForConstString("credit"), - codecForConstString("debit"), - ), - ) - .build("TalerCorebankApi.Balance"); - -const codecForPublicAccount = (): Codec<TalerCorebankApi.PublicAccount> => - buildCodecForObject<TalerCorebankApi.PublicAccount>() - .property("username", codecForString()) - .property("balance", codecForBalance()) - .property("payto_uri", codecForPaytoString()) - .property("is_taler_exchange", codecForBoolean()) - .property("row_id", codecOptional(codecForNumber())) - .build("TalerCorebankApi.PublicAccount"); - -export const codecForPublicAccountsResponse = - (): Codec<TalerCorebankApi.PublicAccountsResponse> => - buildCodecForObject<TalerCorebankApi.PublicAccountsResponse>() - .property("public_accounts", codecForList(codecForPublicAccount())) - .build("TalerCorebankApi.PublicAccountsResponse"); - -export const codecForAccountMinimalData = - (): Codec<TalerCorebankApi.AccountMinimalData> => - buildCodecForObject<TalerCorebankApi.AccountMinimalData>() - .property("username", codecForString()) - .property("name", codecForString()) - .property("payto_uri", codecForPaytoString()) - .property("balance", codecForBalance()) - .property("row_id", codecForNumber()) - .property("debit_threshold", codecForAmountString()) - .property("min_cashout", codecOptional(codecForAmountString())) - .property("is_public", codecForBoolean()) - .property("is_taler_exchange", codecForBoolean()) - .property( - "status", - codecOptional( - codecForEither( - codecForConstString("active"), - codecForConstString("deleted"), - ), - ), - ) - .build("TalerCorebankApi.AccountMinimalData"); - -export const codecForListBankAccountsResponse = - (): Codec<TalerCorebankApi.ListBankAccountsResponse> => - buildCodecForObject<TalerCorebankApi.ListBankAccountsResponse>() - .property("accounts", codecForList(codecForAccountMinimalData())) - .build("TalerCorebankApi.ListBankAccountsResponse"); - -export const codecForAccountData = (): Codec<TalerCorebankApi.AccountData> => - buildCodecForObject<TalerCorebankApi.AccountData>() - .property("name", codecForString()) - .property("balance", codecForBalance()) - .property("payto_uri", codecForPaytoString()) - .property("debit_threshold", codecForAmountString()) - .property("min_cashout", codecOptional(codecForAmountString())) - .property("contact_data", codecOptional(codecForChallengeContactData())) - .property("cashout_payto_uri", codecOptional(codecForPaytoString())) - .property("is_public", codecForBoolean()) - .property("is_taler_exchange", codecForBoolean()) - .property( - "tan_channel", - codecOptional( - codecForEither( - codecForConstString(TalerCorebankApi.TanChannel.SMS), - codecForConstString(TalerCorebankApi.TanChannel.EMAIL), - ), - ), - ) - .property( - "status", - codecOptional( - codecForEither( - codecForConstString("active"), - codecForConstString("deleted"), - ), - ), - ) - .build("TalerCorebankApi.AccountData"); - -export const codecForChallengeContactData = - (): Codec<TalerCorebankApi.ChallengeContactData> => - buildCodecForObject<TalerCorebankApi.ChallengeContactData>() - .property("email", codecOptional(codecForString())) - .property("phone", codecOptional(codecForString())) - .build("TalerCorebankApi.ChallengeContactData"); - -export const codecForWithdrawalPublicInfo = - (): Codec<TalerCorebankApi.WithdrawalPublicInfo> => - buildCodecForObject<TalerCorebankApi.WithdrawalPublicInfo>() - .property( - "status", - codecForEither( - codecForConstString("pending"), - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - ), - ) - .property("amount", codecOptional(codecForAmountString())) - .property("username", codecForString()) - .property("selected_reserve_pub", codecOptional(codecForString())) - .property( - "selected_exchange_account", - codecOptional(codecForPaytoString()), - ) - .build("TalerCorebankApi.WithdrawalPublicInfo"); - -export const codecForBankAccountTransactionsResponse = - (): Codec<TalerCorebankApi.BankAccountTransactionsResponse> => - buildCodecForObject<TalerCorebankApi.BankAccountTransactionsResponse>() - .property( - "transactions", - codecForList(codecForBankAccountTransactionInfo()), - ) - .build("TalerCorebankApi.BankAccountTransactionsResponse"); - -export const codecForBankAccountTransactionInfo = - (): Codec<TalerCorebankApi.BankAccountTransactionInfo> => - buildCodecForObject<TalerCorebankApi.BankAccountTransactionInfo>() - .property("creditor_payto_uri", codecForPaytoString()) - .property("debtor_payto_uri", codecForPaytoString()) - .property("amount", codecForAmountString()) - .property( - "direction", - codecForEither( - codecForConstString("debit"), - codecForConstString("credit"), - ), - ) - .property("subject", codecForString()) - .property("row_id", codecForNumber()) - .property("date", codecForTimestamp) - .build("TalerCorebankApi.BankAccountTransactionInfo"); - -export const codecForCreateTransactionResponse = - (): Codec<TalerCorebankApi.CreateTransactionResponse> => - buildCodecForObject<TalerCorebankApi.CreateTransactionResponse>() - .property("row_id", codecForNumber()) - .build("TalerCorebankApi.CreateTransactionResponse"); - -export const codecForRegisterAccountResponse = - (): Codec<TalerCorebankApi.RegisterAccountResponse> => - buildCodecForObject<TalerCorebankApi.RegisterAccountResponse>() - .property("internal_payto_uri", codecForPaytoString()) - .build("TalerCorebankApi.RegisterAccountResponse"); - -export const codecForBankAccountCreateWithdrawalResponse = - (): Codec<TalerCorebankApi.BankAccountCreateWithdrawalResponse> => - buildCodecForObject<TalerCorebankApi.BankAccountCreateWithdrawalResponse>() - .property("taler_withdraw_uri", codecForTalerUriString()) - .property("withdrawal_id", codecForString()) - .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse"); - -export const codecForCashoutPending = - (): Codec<TalerCorebankApi.CashoutResponse> => - buildCodecForObject<TalerCorebankApi.CashoutResponse>() - .property("cashout_id", codecForNumber()) - .build("TalerCorebankApi.CashoutPending"); - -export const codecForCashoutConversionResponse = - (): Codec<TalerBankConversionApi.CashoutConversionResponse> => - buildCodecForObject<TalerBankConversionApi.CashoutConversionResponse>() - .property("amount_credit", codecForAmountString()) - .property("amount_debit", codecForAmountString()) - .build("TalerCorebankApi.CashoutConversionResponse"); - -export const codecForCashinConversionResponse = - (): Codec<TalerBankConversionApi.CashinConversionResponse> => - buildCodecForObject<TalerBankConversionApi.CashinConversionResponse>() - .property("amount_credit", codecForAmountString()) - .property("amount_debit", codecForAmountString()) - .build("TalerCorebankApi.CashinConversionResponse"); - -export const codecForCashouts = (): Codec<TalerCorebankApi.Cashouts> => - buildCodecForObject<TalerCorebankApi.Cashouts>() - .property("cashouts", codecForList(codecForCashoutInfo())) - .build("TalerCorebankApi.Cashouts"); - -export const codecForCashoutInfo = (): Codec<TalerCorebankApi.CashoutInfo> => - buildCodecForObject<TalerCorebankApi.CashoutInfo>() - .property("cashout_id", codecForNumber()) - .build("TalerCorebankApi.CashoutInfo"); - -export const codecForGlobalCashouts = - (): Codec<TalerCorebankApi.GlobalCashouts> => - buildCodecForObject<TalerCorebankApi.GlobalCashouts>() - .property("cashouts", codecForList(codecForGlobalCashoutInfo())) - .build("TalerCorebankApi.GlobalCashouts"); - -export const codecForGlobalCashoutInfo = - (): Codec<TalerCorebankApi.GlobalCashoutInfo> => - buildCodecForObject<TalerCorebankApi.GlobalCashoutInfo>() - .property("cashout_id", codecForNumber()) - .property("username", codecForString()) - .build("TalerCorebankApi.GlobalCashoutInfo"); - -export const codecForCashoutStatusResponse = - (): Codec<TalerCorebankApi.CashoutStatusResponse> => - buildCodecForObject<TalerCorebankApi.CashoutStatusResponse>() - .property("amount_debit", codecForAmountString()) - .property("amount_credit", codecForAmountString()) - .property("subject", codecForString()) - .property("creation_time", codecForTimestamp) - .build("TalerCorebankApi.CashoutStatusResponse"); - -export const codecForConversionRatesResponse = - (): Codec<TalerCorebankApi.ConversionRatesResponse> => - buildCodecForObject<TalerCorebankApi.ConversionRatesResponse>() - .property("buy_at_ratio", codecForDecimalNumber()) - .property("buy_in_fee", codecForDecimalNumber()) - .property("sell_at_ratio", codecForDecimalNumber()) - .property("sell_out_fee", codecForDecimalNumber()) - .build("TalerCorebankApi.ConversionRatesResponse"); - -export const codecForMonitorResponse = - (): Codec<TalerCorebankApi.MonitorResponse> => - buildCodecForUnion<TalerCorebankApi.MonitorResponse>() - .discriminateOn("type") - .alternative("no-conversions", codecForMonitorNoConversion()) - .alternative("with-conversions", codecForMonitorWithCashout()) - .build("TalerWireGatewayApi.IncomingBankTransaction"); - -export const codecForMonitorNoConversion = - (): Codec<TalerCorebankApi.MonitorNoConversion> => - buildCodecForObject<TalerCorebankApi.MonitorNoConversion>() - .property("type", codecForConstString("no-conversions")) - .property("talerInCount", codecForNumber()) - .property("talerInVolume", codecForAmountString()) - .property("talerOutCount", codecForNumber()) - .property("talerOutVolume", codecForAmountString()) - .build("TalerCorebankApi.MonitorJustPayouts"); - -export const codecForMonitorWithCashout = - (): Codec<TalerCorebankApi.MonitorWithConversion> => - buildCodecForObject<TalerCorebankApi.MonitorWithConversion>() - .property("type", codecForConstString("with-conversions")) - .property("cashinCount", codecForNumber()) - .property("cashinFiatVolume", codecForAmountString()) - .property("cashinRegionalVolume", codecForAmountString()) - .property("cashoutCount", codecForNumber()) - .property("cashoutFiatVolume", codecForAmountString()) - .property("cashoutRegionalVolume", codecForAmountString()) - .property("talerInCount", codecForNumber()) - .property("talerInVolume", codecForAmountString()) - .property("talerOutCount", codecForNumber()) - .property("talerOutVolume", codecForAmountString()) - .build("TalerCorebankApi.MonitorWithCashout"); - -export const codecForBankVersion = - (): Codec<TalerBankIntegrationApi.BankVersion> => - buildCodecForObject<TalerBankIntegrationApi.BankVersion>() - .property("currency", codecForCurrencyName()) - .property("currency_specification", codecForCurrencySpecificiation()) - .property("name", codecForConstString("taler-bank-integration")) - .property("version", codecForLibtoolVersion()) - .build("TalerBankIntegrationApi.BankVersion"); - -export const codecForBankWithdrawalOperationStatus = - (): Codec<TalerBankIntegrationApi.BankWithdrawalOperationStatus> => - buildCodecForObject<TalerBankIntegrationApi.BankWithdrawalOperationStatus>() - .property( - "status", - codecForEither( - codecForConstString("pending"), - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - ), - ) - .property("amount", codecOptional(codecForAmountString())) - .property("currency", codecOptional(codecForCurrencyName())) - .property("suggested_amount", codecOptional(codecForAmountString())) - .property("card_fees", codecOptional(codecForAmountString())) - .property("sender_wire", codecOptional(codecForPaytoString())) - .property("suggested_exchange", codecOptional(codecForURL())) - .property("confirm_transfer_url", codecOptional(codecForURL())) - .property("wire_types", codecForList(codecForString())) - .property("selected_reserve_pub", codecOptional(codecForString())) - .property("selected_exchange_account", codecOptional(codecForString())) - .property("max_amount", codecOptional(codecForAmountString())) - .build("TalerBankIntegrationApi.BankWithdrawalOperationStatus"); - -export const codecForBankWithdrawalOperationPostResponse = - (): Codec<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse> => - buildCodecForObject<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse>() - .property( - "status", - codecForEither( - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - ), - ) - .property("confirm_transfer_url", codecOptional(codecForURL())) - .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse"); - -export const codecForRevenueConfig = (): Codec<TalerRevenueApi.RevenueConfig> => - buildCodecForObject<TalerRevenueApi.RevenueConfig>() - .property("name", codecForConstString("taler-revenue")) - .property("version", codecForString()) - .property("currency", codecForString()) - .property("implementation", codecOptional(codecForString())) - .build("TalerRevenueApi.RevenueConfig"); - -export const codecForRevenueIncomingHistory = - (): Codec<TalerRevenueApi.RevenueIncomingHistory> => - buildCodecForObject<TalerRevenueApi.RevenueIncomingHistory>() - .property("credit_account", codecForPaytoString()) - .property( - "incoming_transactions", - codecForList(codecForRevenueIncomingBankTransaction()), - ) - .build("TalerRevenueApi.MerchantIncomingHistory"); - -export const codecForRevenueIncomingBankTransaction = - (): Codec<TalerRevenueApi.RevenueIncomingBankTransaction> => - buildCodecForObject<TalerRevenueApi.RevenueIncomingBankTransaction>() - .property("amount", codecForAmountString()) - .property("date", codecForTimestamp) - .property("debit_account", codecForPaytoString()) - .property("row_id", codecForNumber()) - .property("subject", codecForString()) - .build("TalerRevenueApi.RevenueIncomingBankTransaction"); - -export const codecForTransferResponse = - (): Codec<TalerWireGatewayApi.TransferResponse> => - buildCodecForObject<TalerWireGatewayApi.TransferResponse>() - .property("row_id", codecForNumber()) - .property("timestamp", codecForTimestamp) - .build("TalerWireGatewayApi.TransferResponse"); - -export const codecForIncomingHistory = - (): Codec<TalerWireGatewayApi.IncomingHistory> => - buildCodecForObject<TalerWireGatewayApi.IncomingHistory>() - .property("credit_account", codecForPaytoString()) - .property( - "incoming_transactions", - codecForList(codecForIncomingBankTransaction()), - ) - .build("TalerWireGatewayApi.IncomingHistory"); - -export const codecForIncomingBankTransaction = - (): Codec<TalerWireGatewayApi.IncomingBankTransaction> => - buildCodecForUnion<TalerWireGatewayApi.IncomingBankTransaction>() - .discriminateOn("type") - .alternative("RESERVE", codecForIncomingReserveTransaction()) - .alternative("WAD", codecForIncomingWadTransaction()) - .build("TalerWireGatewayApi.IncomingBankTransaction"); - -export const codecForIncomingReserveTransaction = - (): Codec<TalerWireGatewayApi.IncomingReserveTransaction> => - buildCodecForObject<TalerWireGatewayApi.IncomingReserveTransaction>() - .property("amount", codecForAmountString()) - .property("date", codecForTimestamp) - .property("debit_account", codecForPaytoString()) - .property("reserve_pub", codecForString()) - .property("row_id", codecForNumber()) - .property("type", codecForConstString("RESERVE")) - .build("TalerWireGatewayApi.IncomingReserveTransaction"); - -export const codecForIncomingWadTransaction = - (): Codec<TalerWireGatewayApi.IncomingWadTransaction> => - buildCodecForObject<TalerWireGatewayApi.IncomingWadTransaction>() - .property("amount", codecForAmountString()) - .property("credit_account", codecForPaytoString()) - .property("date", codecForTimestamp) - .property("debit_account", codecForPaytoString()) - .property("origin_exchange_url", codecForURL()) - .property("row_id", codecForNumber()) - .property("type", codecForConstString("WAD")) - .property("wad_id", codecForString()) - .build("TalerWireGatewayApi.IncomingWadTransaction"); - -export const codecForOutgoingHistory = - (): Codec<TalerWireGatewayApi.OutgoingHistory> => - buildCodecForObject<TalerWireGatewayApi.OutgoingHistory>() - .property("debit_account", codecForPaytoString()) - .property( - "outgoing_transactions", - codecForList(codecForOutgoingBankTransaction()), - ) - .build("TalerWireGatewayApi.OutgoingHistory"); - -export const codecForOutgoingBankTransaction = - (): Codec<TalerWireGatewayApi.OutgoingBankTransaction> => - buildCodecForObject<TalerWireGatewayApi.OutgoingBankTransaction>() - .property("amount", codecForAmountString()) - .property("credit_account", codecForPaytoString()) - .property("date", codecForTimestamp) - .property("exchange_base_url", codecForURL()) - .property("row_id", codecForNumber()) - .property("wtid", codecForString()) - .build("TalerWireGatewayApi.OutgoingBankTransaction"); - -export const codecForAddIncomingResponse = - (): Codec<TalerWireGatewayApi.AddIncomingResponse> => - buildCodecForObject<TalerWireGatewayApi.AddIncomingResponse>() - .property("row_id", codecForNumber()) - .property("timestamp", codecForTimestamp) - .build("TalerWireGatewayApi.AddIncomingResponse"); - -export const codecForAmlRecords = (): Codec<TalerExchangeApi.AmlRecords> => - buildCodecForObject<TalerExchangeApi.AmlRecords>() - .property("records", codecForList(codecForAmlRecord())) - .build("TalerExchangeApi.AmlRecords"); - -export const codecForAmlRecord = (): Codec<TalerExchangeApi.AmlRecord> => - buildCodecForObject<TalerExchangeApi.AmlRecord>() - .property("current_state", codecForNumber()) - .property("h_payto", codecForString()) - .property("rowid", codecForNumber()) - .property("threshold", codecForAmountString()) - .build("TalerExchangeApi.AmlRecord"); - -export const codecForAmlDecisionDetails = - (): Codec<TalerExchangeApi.AmlDecisionDetails> => - buildCodecForObject<TalerExchangeApi.AmlDecisionDetails>() - .property("aml_history", codecForList(codecForAmlDecisionDetail())) - .property("kyc_attributes", codecForList(codecForKycDetail())) - .build("TalerExchangeApi.AmlDecisionDetails"); - -export const codecForAmlDecisionDetail = - (): Codec<TalerExchangeApi.AmlDecisionDetail> => - buildCodecForObject<TalerExchangeApi.AmlDecisionDetail>() - .property("justification", codecForString()) - .property("new_state", codecForNumber()) - .property("decision_time", codecForTimestamp) - .property("new_threshold", codecForAmountString()) - .property("decider_pub", codecForString()) - .build("TalerExchangeApi.AmlDecisionDetail"); - -export const codecForChallenge = (): Codec<TalerCorebankApi.Challenge> => - buildCodecForObject<TalerCorebankApi.Challenge>() - .property("challenge_id", codecForNumber()) - .build("TalerCorebankApi.Challenge"); - -export const codecForTanTransmission = - (): Codec<TalerCorebankApi.TanTransmission> => - buildCodecForObject<TalerCorebankApi.TanTransmission>() - .property( - "tan_channel", - codecForEither( - codecForConstString(TalerCorebankApi.TanChannel.SMS), - codecForConstString(TalerCorebankApi.TanChannel.EMAIL), - ), - ) - .property("tan_info", codecForString()) - .build("TalerCorebankApi.TanTransmission"); - -interface KycDetail { - provider_section: string; - attributes?: Object; - collection_time: Timestamp; - expiration_time: Timestamp; -} -export const codecForKycDetail = (): Codec<TalerExchangeApi.KycDetail> => - buildCodecForObject<TalerExchangeApi.KycDetail>() - .property("provider_section", codecForString()) - .property("attributes", codecOptional(codecForAny())) - .property("collection_time", codecForTimestamp) - .property("expiration_time", codecForTimestamp) - .build("TalerExchangeApi.KycDetail"); - -export const codecForAmlDecision = (): Codec<TalerExchangeApi.AmlDecision> => - buildCodecForObject<TalerExchangeApi.AmlDecision>() - .property("justification", codecForString()) - .property("new_threshold", codecForAmountString()) - .property("h_payto", codecForString()) - .property("new_state", codecForNumber()) - .property("officer_sig", codecForString()) - .property("decision_time", codecForTimestamp) - .property("kyc_requirements", codecOptional(codecForList(codecForString()))) - .build("TalerExchangeApi.AmlDecision"); - -export const codecForConversionInfo = - (): Codec<TalerBankConversionApi.ConversionInfo> => - buildCodecForObject<TalerBankConversionApi.ConversionInfo>() - .property("cashin_fee", codecForAmountString()) - .property("cashin_min_amount", codecForAmountString()) - .property("cashin_ratio", codecForDecimalNumber()) - .property( - "cashin_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) - .property("cashin_tiny_amount", codecForAmountString()) - .property("cashout_fee", codecForAmountString()) - .property("cashout_min_amount", codecForAmountString()) - .property("cashout_ratio", codecForDecimalNumber()) - .property( - "cashout_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) - .property("cashout_tiny_amount", codecForAmountString()) - .build("ConversionBankConfig.ConversionInfo"); - -export const codecForConversionBankConfig = - (): Codec<TalerBankConversionApi.IntegrationConfig> => - buildCodecForObject<TalerBankConversionApi.IntegrationConfig>() - .property("name", codecForConstString("taler-conversion-info")) - .property("version", codecForString()) - .property("regional_currency", codecForString()) - .property( - "regional_currency_specification", - codecForCurrencySpecificiation(), - ) - .property("fiat_currency", codecForString()) - .property("fiat_currency_specification", codecForCurrencySpecificiation()) - - .property("conversion_rate", codecForConversionInfo()) - .build("ConversionBankConfig.IntegrationConfig"); - -export const codecForChallengerTermsOfServiceResponse = - (): Codec<ChallengerApi.ChallengerTermsOfServiceResponse> => - buildCodecForObject<ChallengerApi.ChallengerTermsOfServiceResponse>() - .property("name", codecForConstString("challenger")) - .property("version", codecForString()) - .property("implementation", codecOptional(codecForString())) - .property("restrictions", codecOptional(codecForMap(codecForAny()))) - .property( - "address_type", - codecForEither( - codecForConstString("phone"), - codecForConstString("email"), - ), - ) - .build("ChallengerApi.ChallengerTermsOfServiceResponse"); - -export const codecForChallengeSetupResponse = - (): Codec<ChallengerApi.ChallengeSetupResponse> => - buildCodecForObject<ChallengerApi.ChallengeSetupResponse>() - .property("nonce", codecForString()) - .build("ChallengerApi.ChallengeSetupResponse"); - -export const codecForChallengeStatus = - (): Codec<ChallengerApi.ChallengeStatus> => - buildCodecForObject<ChallengerApi.ChallengeStatus>() - .property("fix_address", codecForBoolean()) - .property("solved", codecForBoolean()) - .property("last_address", codecOptional(codecForMap(codecForAny()))) - .property("changes_left", codecForNumber()) - .property("retransmission_time", codecForTimestamp) - .property("pin_transmissions_left", codecForNumber()) - .property("auth_attempts_left", codecForNumber()) - .build("ChallengerApi.ChallengeStatus"); - -export const codecForChallengeResponse = - (): Codec<ChallengerApi.ChallengeResponse> => - buildCodecForUnion<ChallengerApi.ChallengeResponse>() - .discriminateOn("type") - .alternative("completed", codecForChallengeRedirect()) - .alternative("created", codecForChallengeCreateResponse()) - .build("ChallengerApi.ChallengeResponse"); - -export const codecForChallengeCreateResponse = - (): Codec<ChallengerApi.ChallengeCreateResponse> => - buildCodecForObject<ChallengerApi.ChallengeCreateResponse>() - .property("attempts_left", codecForNumber()) - .property("type", codecForConstString("created")) - .property("address", codecForAny()) - .property("transmitted", codecForBoolean()) - .property("retransmission_time", codecForTimestamp) - .build("ChallengerApi.ChallengeCreateResponse"); - -export const codecForChallengeRedirect = - (): Codec<ChallengerApi.ChallengeRedirect> => - buildCodecForObject<ChallengerApi.ChallengeRedirect>() - .property("type", codecForConstString("completed")) - .property("redirect_url", codecForString()) - .build("ChallengerApi.ChallengeRedirect"); - -export const codecForChallengeInvalidPinResponse = - (): Codec<ChallengerApi.InvalidPinResponse> => - buildCodecForObject<ChallengerApi.InvalidPinResponse>() - .property("ec", codecOptional(codecForNumber())) - .property("hint", codecForAny()) - .property("type", codecForConstString("pending")) - .property("addresses_left", codecForNumber()) - .property("pin_transmissions_left", codecForNumber()) - .property("auth_attempts_left", codecForNumber()) - .property("exhausted", codecForBoolean()) - .property("no_challenge", codecForBoolean()) - .build("ChallengerApi.InvalidPinResponse"); - -export const codecForChallengeSolveResponse = - (): Codec<ChallengerApi.ChallengeSolveResponse> => - buildCodecForUnion<ChallengerApi.ChallengeSolveResponse>() - .discriminateOn("type") - .alternative("completed", codecForChallengeRedirect()) - .alternative("pending", codecForChallengeInvalidPinResponse()) - .build("ChallengerApi.ChallengeSolveResponse"); - -export const codecForChallengerAuthResponse = - (): Codec<ChallengerApi.ChallengerAuthResponse> => - buildCodecForObject<ChallengerApi.ChallengerAuthResponse>() - .property("access_token", codecForString()) - .property("token_type", codecForAny()) - .property("expires_in", codecForNumber()) - .build("ChallengerApi.ChallengerAuthResponse"); - -export const codecForChallengerInfoResponse = - (): Codec<ChallengerApi.ChallengerInfoResponse> => - buildCodecForObject<ChallengerApi.ChallengerInfoResponse>() - .property("id", codecForNumber()) - .property("address", codecForAny()) - .property("address_type", codecForString()) - .property("expires", codecForTimestamp) - .build("ChallengerApi.ChallengerInfoResponse"); - -export const codecForTemplateEditableDetails = - (): Codec<TalerMerchantApi.TemplateEditableDetails> => - buildCodecForObject<TalerMerchantApi.TemplateEditableDetails>() - .property("summary", codecOptional(codecForString())) - .property("currency", codecOptional(codecForString())) - .property("amount", codecOptional(codecForAmountString())) - .build("TemplateEditableDetails"); - -export const codecForMerchantReserveCreateConfirmation = - (): Codec<TalerMerchantApi.MerchantReserveCreateConfirmation> => - buildCodecForObject<TalerMerchantApi.MerchantReserveCreateConfirmation>() - .property("accounts", codecForList(codecForExchangeWireAccount())) - .property("reserve_pub", codecForString()) - .build("MerchantReserveCreateConfirmation"); - -type EmailAddress = string; -type PhoneNumber = string; -type EddsaSignature = string; -// base32 encoded RSA blinded signature. -type BlindedRsaSignature = string; -type Base32 = string; - -type DecimalNumber = string; -type RsaSignature = string; -type Float = number; -type LibtoolVersion = string; -// The type of a coin's blinded envelope depends on the cipher that is used -// for signing with a denomination key. -type CoinEnvelope = RSACoinEnvelope | CSCoinEnvelope; -// For denomination signatures based on RSA, the planchet is just a blinded -// coin's public EdDSA key. -interface RSACoinEnvelope { - cipher: "RSA" | "RSA+age_restricted"; - rsa_blinded_planchet: string; // Crockford Base32 encoded -} -// For denomination signatures based on Blind Clause-Schnorr, the planchet -// consists of the public nonce and two Curve25519 scalars which are two -// blinded challenges in the Blinded Clause-Schnorr signature scheme. -// See https://taler.net/papers/cs-thesis.pdf for details. -interface CSCoinEnvelope { - cipher: "CS" | "CS+age_restricted"; - cs_nonce: string; // Crockford Base32 encoded - cs_blinded_c0: string; // Crockford Base32 encoded - cs_blinded_c1: string; // Crockford Base32 encoded -} -// Secret for blinding/unblinding. -// An RSA blinding secret, which is basically -// a 256-bit nonce, converted to Crockford Base32. -type DenominationBlindingKeyP = string; - -//FIXME: implement this codec -const codecForURL = codecForString; -//FIXME: implement this codec -const codecForLibtoolVersion = codecForString; -//FIXME: implement this codec -const codecForCurrencyName = codecForString; -//FIXME: implement this codec -const codecForDecimalNumber = codecForString; - -export type WithdrawalOperationStatus = - | "pending" - | "selected" - | "aborted" - | "confirmed"; - -export namespace TalerWireGatewayApi { - export interface TransferResponse { - // Timestamp that indicates when the wire transfer will be executed. - // In cases where the wire transfer gateway is unable to know when - // the wire transfer will be executed, the time at which the request - // has been received and stored will be returned. - // The purpose of this field is for debugging (humans trying to find - // the transaction) as well as for taxation (determining which - // time period a transaction belongs to). - timestamp: Timestamp; - - // Opaque ID of the transaction that the bank has made. - row_id: SafeUint64; - } - - export interface TransferRequest { - // Nonce to make the request idempotent. Requests with the same - // transaction_uid that differ in any of the other fields - // are rejected. - request_uid: HashCode; - - // Amount to transfer. - amount: AmountString; - - // Base URL of the exchange. Shall be included by the bank gateway - // in the appropriate section of the wire transfer details. - exchange_base_url: string; - - // Wire transfer identifier chosen by the exchange, - // used by the merchant to identify the Taler order(s) - // associated with this wire transfer. - wtid: ShortHashCode; - - // The recipient's account identifier as a payto URI. - credit_account: PaytoString; - } - - export interface IncomingHistory { - // Array of incoming transactions. - incoming_transactions: IncomingBankTransaction[]; - - // Payto URI to identify the receiver of funds. - // This must be one of the exchange's bank accounts. - // Credit account is shared by all incoming transactions - // as per the nature of the request. - - // undefined if incoming transaction is empty - credit_account?: PaytoString; - } - - // Union discriminated by the "type" field. - export type IncomingBankTransaction = - | IncomingReserveTransaction - | IncomingWadTransaction; - - export interface IncomingReserveTransaction { - type: "RESERVE"; - - // Opaque identifier of the returned record. - row_id: SafeUint64; - - // Date of the transaction. - date: Timestamp; - - // Amount transferred. - amount: AmountString; - - // Payto URI to identify the sender of funds. - debit_account: PaytoString; - - // The reserve public key extracted from the transaction details. - reserve_pub: EddsaPublicKey; - } - - export interface IncomingWadTransaction { - type: "WAD"; - - // Opaque identifier of the returned record. - row_id: SafeUint64; - - // Date of the transaction. - date: Timestamp; - - // Amount transferred. - amount: AmountString; - - // Payto URI to identify the receiver of funds. - // This must be one of the exchange's bank accounts. - credit_account: PaytoString; - - // Payto URI to identify the sender of funds. - debit_account: PaytoString; - - // Base URL of the exchange that originated the wad. - origin_exchange_url: string; - - // The reserve public key extracted from the transaction details. - wad_id: WadId; - } - - export interface OutgoingHistory { - // Array of outgoing transactions. - outgoing_transactions: OutgoingBankTransaction[]; - - // Payto URI to identify the sender of funds. - // This must be one of the exchange's bank accounts. - // Credit account is shared by all incoming transactions - // as per the nature of the request. - - // undefined if outgoing transactions is empty - debit_account?: PaytoString; - } - - export interface OutgoingBankTransaction { - // Opaque identifier of the returned record. - row_id: SafeUint64; - - // Date of the transaction. - date: Timestamp; - - // Amount transferred. - amount: AmountString; - - // Payto URI to identify the receiver of funds. - credit_account: PaytoString; - - // The wire transfer ID in the outgoing transaction. - wtid: ShortHashCode; - - // Base URL of the exchange. - exchange_base_url: string; - } - - export interface AddIncomingRequest { - // Amount to transfer. - amount: AmountString; - - // Reserve public key that is included in the wire transfer details - // to identify the reserve that is being topped up. - reserve_pub: EddsaPublicKey; - - // Account (as payto URI) that makes the wire transfer to the exchange. - // Usually this account must be created by the test harness before this API is - // used. An exception is the "exchange-fakebank", where any debit account can be - // specified, as it is automatically created. - debit_account: PaytoString; - } - - export interface AddIncomingResponse { - // Timestamp that indicates when the wire transfer will be executed. - // In cases where the wire transfer gateway is unable to know when - // the wire transfer will be executed, the time at which the request - // has been received and stored will be returned. - // The purpose of this field is for debugging (humans trying to find - // the transaction) as well as for taxation (determining which - // time period a transaction belongs to). - timestamp: Timestamp; - - // Opaque ID of the transaction that the bank has made. - row_id: SafeUint64; - } -} - -export namespace TalerRevenueApi { - export interface RevenueConfig { - // Name of the API. - name: "taler-revenue"; - - // libtool-style representation of the Bank protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Currency used by this gateway. - currency: string; - - // URN of the implementation (needed to interpret 'revision' in version). - // @since v0, may become mandatory in the future. - implementation?: string; - } - - export interface RevenueIncomingHistory { - // Array of incoming transactions. - incoming_transactions: RevenueIncomingBankTransaction[]; - - // Payto URI to identify the receiver of funds. - // Credit account is shared by all incoming transactions - // as per the nature of the request. - credit_account: string; - } - - export interface RevenueIncomingBankTransaction { - // Opaque identifier of the returned record. - row_id: SafeUint64; - - // Date of the transaction. - date: Timestamp; - - // Amount transferred. - amount: AmountString; - - // Payto URI to identify the sender of funds. - debit_account: string; - - // The wire transfer subject. - subject: string; - } -} - -export namespace TalerBankConversionApi { - export interface ConversionInfo { - // Exchange rate to buy regional currency from fiat - cashin_ratio: DecimalNumber; - - // Exchange rate to sell regional currency for fiat - cashout_ratio: DecimalNumber; - - // Fee to subtract after applying the cashin ratio. - cashin_fee: AmountString; - - // Fee to subtract after applying the cashout ratio. - cashout_fee: AmountString; - - // Minimum amount authorised for cashin, in fiat before conversion - cashin_min_amount: AmountString; - - // Minimum amount authorised for cashout, in regional before conversion - cashout_min_amount: AmountString; - - // Smallest possible regional amount, converted amount is rounded to this amount - cashin_tiny_amount: AmountString; - - // Smallest possible fiat amount, converted amount is rounded to this amount - cashout_tiny_amount: AmountString; - - // Rounding mode used during cashin conversion - cashin_rounding_mode: "zero" | "up" | "nearest"; - - // Rounding mode used during cashout conversion - cashout_rounding_mode: "zero" | "up" | "nearest"; - } - - export interface IntegrationConfig { - // libtool-style representation of the Bank protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Name of the API. - name: "taler-conversion-info"; - - // Currency used by this bank. - regional_currency: string; - - // How the bank SPA should render this currency. - regional_currency_specification: CurrencySpecification; - - // External currency used during conversion. - fiat_currency: string; - - // How the bank SPA should render this currency. - fiat_currency_specification: CurrencySpecification; - - // Extra conversion rate information. - // Only present if server opts in to report the static conversion rate. - conversion_rate: ConversionInfo; - } - - export interface CashinConversionResponse { - // Amount that the user will get deducted from their fiat - // bank account, according to the 'amount_credit' value. - amount_debit: AmountString; - // Amount that the user will receive in their regional - // bank account, according to 'amount_debit'. - amount_credit: AmountString; - } - - export interface CashoutConversionResponse { - // Amount that the user will get deducted from their regional - // bank account, according to the 'amount_credit' value. - amount_debit: AmountString; - // Amount that the user will receive in their fiat - // bank account, according to 'amount_debit'. - amount_credit: AmountString; - } - - export type RoundingMode = "zero" | "up" | "nearest"; - - export interface ConversionRate { - // Exchange rate to buy regional currency from fiat - cashin_ratio: DecimalNumber; - - // Fee to subtract after applying the cashin ratio. - cashin_fee: AmountString; - - // Minimum amount authorised for cashin, in fiat before conversion - cashin_min_amount: AmountString; - - // Smallest possible regional amount, converted amount is rounded to this amount - cashin_tiny_amount: AmountString; - - // Rounding mode used during cashin conversion - cashin_rounding_mode: RoundingMode; - - // Exchange rate to sell regional currency for fiat - cashout_ratio: DecimalNumber; - - // Fee to subtract after applying the cashout ratio. - cashout_fee: AmountString; - - // Minimum amount authorised for cashout, in regional before conversion - cashout_min_amount: AmountString; - - // Smallest possible fiat amount, converted amount is rounded to this amount - cashout_tiny_amount: AmountString; - - // Rounding mode used during cashout conversion - cashout_rounding_mode: RoundingMode; - } -} - -export namespace TalerBankIntegrationApi { - export interface BankVersion { - // libtool-style representation of the Bank protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Currency used by this bank. - currency: string; - - // How the bank SPA should render this currency. - currency_specification?: CurrencySpecification; - - // Name of the API. - name: "taler-bank-integration"; - } - - export interface BankWithdrawalOperationStatus { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: WithdrawalOperationStatus; - - // Currency used for the withdrawal. - // MUST be present when amount is absent. - // @since v2, may become mandatory in the future. - currency?: string; - - // Amount that will be withdrawn with this operation - // (raw amount without fee considerations). Only - // given once the amount is fixed and cannot be changed. - // Optional since **vC2EC**. - amount?: AmountString | undefined; - - // Suggestion for the amount to be withdrawn with this - // operation. Given if a suggestion was made but the - // user may still change the amount. - // Optional since **vC2EC**. - suggested_amount?: AmountString | undefined; - - // Maximum amount that the wallet can choose to withdraw. - // Only applicable when the amount is not fixed. - // @since **vC2EC**. - max_amount?: AmountString | undefined; - - // The non-Taler card fees the customer will have - // to pay to the bank / payment service provider - // they are using to make the withdrawal. - // @since **vC2EC** - card_fees?: AmountString | undefined; - - // Bank account of the customer that is debiting, as an - // RFC 8905 payto URI. - sender_wire?: PaytoString; - - // Base URL of the suggested exchange. The bank may have - // neither a suggestion nor a requirement for the exchange. - // This value is typically set in the bank's configuration. - suggested_exchange?: string; - - // Base URL of an exchange that must be used. Optional, - // not given *unless* a particular exchange is mandatory. - // This value is typically set in the bank's configuration. - // @since **vC2EC** - required_exchange?: string; - - // URL that the user needs to navigate to in order to - // complete some final confirmation (e.g. 2FA). - // Only applicable when status is selected or pending. - // It may contain the withdrawal operation id. - confirm_transfer_url?: string; - - // Wire transfer types supported by the bank. - wire_types: string[]; - - // Reserve public key selected by the exchange, - // only non-null if status is selected or confirmed. - selected_reserve_pub?: string; - - // Exchange account selected by the wallet; - // only non-null if status is selected or confirmed. - // @since **v1** - selected_exchange_account?: string; - } - - export interface BankWithdrawalOperationPostRequest { - // Reserve public key that should become the wire transfer - // subject to fund the withdrawal. - reserve_pub: string; - - // Payto address of the exchange selected for the withdrawal. - selected_exchange: PaytoString; - - // Selected amount to be transferred. Optional if the - // backend already knows the amount. - // @since **vC2EC** - amount?: AmountString | undefined; - } - - export interface BankWithdrawalOperationPostResponse { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: Omit<"pending", WithdrawalOperationStatus>; - - // URL that the user needs to navigate to in order to - // complete some final confirmation (e.g. 2FA). - // - // Only applicable when status is selected or pending. - // It may contain withdrawal operation id - confirm_transfer_url?: string; - } -} - -export namespace TalerCorebankApi { - export interface IntegrationConfig { - // libtool-style representation of the Bank protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - currency: string; - - // How the bank SPA should render this currency. - currency_specification: CurrencySpecification; - - // Name of the API. - name: "taler-bank-integration"; - } - export interface Config { - // Name of this API, always "taler-corebank". - name: "libeufin-bank"; - // name: "taler-corebank"; - - // API version in the form $n:$n:$n - version: string; - - // Bank display name to be used in user interfaces. - // For consistency use "Taler Bank" if missing. - // @since v4, will become mandatory in the next version. - bank_name: string; - - // Advertised base URL to use when you sharing an URL with another - // program. - // @since v4. - base_url?: string; - - // If 'true' the server provides local currency conversion support - // If 'false' some parts of the API are not supported and return 501 - allow_conversion: boolean; - - // If 'true' anyone can register - // If 'false' only the admin can - allow_registrations: boolean; - - // If 'true' account can delete themselves - // If 'false' only the admin can delete accounts - allow_deletions: boolean; - - // If 'true' anyone can edit their name - // If 'false' only admin can - allow_edit_name: boolean; - - // If 'true' anyone can edit their cashout account - // If 'false' only the admin - allow_edit_cashout_payto_uri: boolean; - - // Default debt limit for newly created accounts - default_debit_threshold: AmountString; - - // Currency used by this bank. - currency: string; - - // How the bank SPA should render this currency. - currency_specification: CurrencySpecification; - - // TAN channels supported by the server - supported_tan_channels: TanChannel[]; - - // Wire transfer type supported by the bank. - // Default to 'iban' is missing - // @since v4, may become mandatory in the future. - wire_type: string; - - // Wire transfer execution fees. - // @since v4, will become mandatory in the next version. - wire_transfer_fees?: AmountString; - } - - export interface BankAccountCreateWithdrawalRequest { - // Amount to withdraw. If given, the wallet - // cannot change the amount. - // Optional since **vC2EC**. - amount?: AmountString; - - // Suggested amount to withdraw. The wallet can - // still change the suggestion. - // @since **vC2EC** - suggested_amount?: AmountString; - } - - export interface BankAccountCreateWithdrawalResponse { - // ID of the withdrawal, can be used to view/modify the withdrawal operation. - withdrawal_id: string; - - // URI that can be passed to the wallet to initiate the withdrawal. - taler_withdraw_uri: TalerUriString; - } - export interface WithdrawalPublicInfo { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: WithdrawalOperationStatus; - - // Amount that will be withdrawn with this operation - // (raw amount without fee considerations). - amount?: AmountString; - - // Suggestion for the amount to be withdrawn with this - // operation. Given if a suggestion was made but the - // user may still change the amount. - // Optional since **vC2EC**. - suggested_amount?: AmountString; - - // Account username - username: string; - - // Reserve public key selected by the exchange, - // only non-null if status is selected or confirmed. - selected_reserve_pub?: string; - - // Exchange account selected by the wallet - // only non-null if status is selected or confirmed. - selected_exchange_account?: PaytoString; - } - - export interface BankAccountTransactionsResponse { - transactions: BankAccountTransactionInfo[]; - } - - export interface BankAccountTransactionInfo { - creditor_payto_uri: PaytoString; - debtor_payto_uri: PaytoString; - - amount: AmountString; - direction: "debit" | "credit"; - - subject: string; - - // Transaction unique ID. Matches - // $transaction_id from the URI. - row_id: number; - date: Timestamp; - } - - export interface CreateTransactionRequest { - // Address in the Payto format of the wire transfer receiver. - // It needs at least the 'message' query string parameter. - payto_uri: PaytoString; - - // Transaction amount (in the $currency:x.y format), optional. - // However, when not given, its value must occupy the 'amount' - // query string parameter of the 'payto' field. In case it - // is given in both places, the paytoUri's takes the precedence. - amount?: AmountString; - - // Nonce to make the request idempotent. Requests with the same - // request_uid that differ in any of the other fields - // are rejected. - // @since v4, will become mandatory in the next version. - request_uid?: ShortHashCode; - } - - export interface CreateTransactionResponse { - // ID identifying the transaction being created - row_id: Integer; - } - - export interface RegisterAccountResponse { - // Internal payto URI of this bank account. - internal_payto_uri: PaytoString; - } - - export interface RegisterAccountRequest { - // Username - username: string; - - // Password. - password: string; - - // Legal name of the account owner - name: string; - - // Defaults to false. - is_public?: boolean; - - // Is this a taler exchange account? - // If true: - // - incoming transactions to the account that do not - // have a valid reserve public key are automatically - // - the account provides the taler-wire-gateway-api endpoints - // Defaults to false. - is_taler_exchange?: boolean; - - // Addresses where to send the TAN for transactions. - contact_data?: ChallengeContactData; - - // 'payto' address of a fiat bank account. - // Payments will be sent to this bank account - // when the user wants to convert the regional currency - // back to fiat currency outside bank. - cashout_payto_uri?: PaytoString; - - // Internal payto URI of this bank account. - // Used mostly for testing. - payto_uri?: PaytoString; - - // If present, set the max debit allowed for this user - // Only admin can set this property. - debit_threshold?: AmountString; - - // If present, set a custom minimum cashout amount for this account. - // Only admin can set this property - // @since v4 - min_cashout?: AmountString; - - // If present, enables 2FA and set the TAN channel used for challenges - // Only admin can set this property, other user can reconfig their account - // after creation. - tan_channel?: TanChannel; - } - - export interface ChallengeContactData { - // E-Mail address - email?: EmailAddress; - - // Phone number. - phone?: PhoneNumber; - } - - export interface AccountReconfiguration { - // Addresses where to send the TAN for transactions. - // Currently only used for cashouts. - // If missing, cashouts will fail. - // In the future, might be used for other transactions - // as well. - // Only admin can change this property. - contact_data?: ChallengeContactData; - - // 'payto' URI of a fiat bank account. - // Payments will be sent to this bank account - // when the user wants to convert the regional currency - // back to fiat currency outside bank. - // Only admin can change this property if not allowed in config - cashout_payto_uri?: PaytoString; - - // If present, change the legal name associated with $username. - // Only admin can change this property if not allowed in config - name?: string; - - // Make this account visible to anyone? - is_public?: boolean; - - // If present, change the max debit allowed for this user - // Only admin can change this property. - debit_threshold?: AmountString; - - // If present, change the custom minimum cashout amount for this account. - // Only admin can set this property - // @since v4 - min_cashout?: AmountString; - - // If present, enables 2FA and set the TAN channel used for challenges - tan_channel?: TanChannel | null; - } - - export interface AccountPasswordChange { - // New password. - new_password: string; - // Old password. If present, check that the old password matches. - // Optional for admin account. - old_password?: string; - } - - export interface PublicAccountsResponse { - public_accounts: PublicAccount[]; - } - export interface PublicAccount { - // Username of the account - username: string; - - // Internal payto URI of this bank account. - payto_uri: string; - - // Current balance of the account - balance: Balance; - - // Is this a taler exchange account? - is_taler_exchange: boolean; - - // Opaque unique ID used for pagination. - // @since v4, will become mandatory in the future. - row_id?: Integer; - } - - export interface ListBankAccountsResponse { - accounts: AccountMinimalData[]; - } - export interface Balance { - amount: AmountString; - credit_debit_indicator: "credit" | "debit"; - } - export interface AccountMinimalData { - // Username - username: string; - - // Legal name of the account owner. - name: string; - - // Internal payto URI of this bank account. - payto_uri: PaytoString; - - // current balance of the account - balance: Balance; - - // Number indicating the max debit allowed for the requesting user. - debit_threshold: AmountString; - - // Custom minimum cashout amount for this account. - // If null or absent, the global conversion fee is used. - // @since v4 - min_cashout?: AmountString; - - // Is this account visible to anyone? - is_public: boolean; - - // Is this a taler exchange account? - is_taler_exchange: boolean; - - // Opaque unique ID used for pagination. - // @since v4, will become mandatory in the future. - row_id?: Integer; - - // Current status of the account - // active: the account can be used - // deleted: the account has been deleted but is retained for compliance - // reasons, only the administrator can access it - // Default to 'active' is missing - // @since v4, will become mandatory in the next version. - status?: "active" | "deleted"; - } - - export interface AccountData { - // Legal name of the account owner. - name: string; - - // Available balance on the account. - balance: Balance; - - // payto://-URI of the account. - payto_uri: PaytoString; - - // Number indicating the max debit allowed for the requesting user. - debit_threshold: AmountString; - - // Custom minimum cashout amount for this account. - // If null or absent, the global conversion fee is used. - // @since v4 - min_cashout?: AmountString; - - contact_data?: ChallengeContactData; - - // 'payto' address pointing the bank account - // where to send cashouts. This field is optional - // because not all the accounts are required to participate - // in the merchants' circuit. One example is the exchange: - // that never cashouts. Registering these accounts can - // be done via the access API. - cashout_payto_uri?: PaytoString; - - // Is this account visible to anyone? - is_public: boolean; - - // Is this a taler exchange account? - is_taler_exchange: boolean; - - // Is 2FA enabled and what channel is used for challenges? - tan_channel?: TanChannel; - - // Current status of the account - // active: the account can be used - // deleted: the account has been deleted but is retained for compliance - // reasons, only the administrator can access it - // Default to 'active' is missing - // @since v4, will become mandatory in the next version. - status?: "active" | "deleted"; - } - - export interface CashoutRequest { - // Nonce to make the request idempotent. Requests with the same - // request_uid that differ in any of the other fields - // are rejected. - request_uid: ShortHashCode; - - // Optional subject to associate to the - // cashout operation. This data will appear - // as the incoming wire transfer subject in - // the user's fiat bank account. - subject?: string; - - // That is the plain amount that the user specified - // to cashout. Its $currency is the (regional) currency of the - // bank instance. - amount_debit: AmountString; - - // That is the amount that will effectively be - // transferred by the bank to the user's bank - // account, that is external to the regional currency. - // It is expressed in the fiat currency and - // is calculated after the cashout fee and the - // exchange rate. See the /cashout-rates call. - // The client needs to calculate this amount - // correctly based on the amount_debit and the cashout rate, - // otherwise the request will fail. - amount_credit: AmountString; - } - - export interface CashoutResponse { - // ID identifying the operation being created - cashout_id: number; - } - - /** - * @deprecated since 4, use 2fa - */ - export interface CashoutConfirmRequest { - // the TAN that confirms $CASHOUT_ID. - tan: string; - } +export const codecForInstancesResponse = + (): Codec<TalerMerchantApi.InstancesResponse> => + buildCodecForObject<TalerMerchantApi.InstancesResponse>() + .property("instances", codecForList(codecForInstance())) + .build("TalerMerchantApi.InstancesResponse"); - export interface Cashouts { - // Every string represents a cash-out operation ID. - cashouts: CashoutInfo[]; - } +export const codecForInstance = (): Codec<TalerMerchantApi.Instance> => + buildCodecForObject<TalerMerchantApi.Instance>() + .property("name", codecForString()) + .property("user_type", codecForString()) + .property("website", codecOptional(codecForString())) + .property("logo", codecOptional(codecForString())) + .property("id", codecForString()) + .property("merchant_pub", codecForString()) + .property("payment_targets", codecForList(codecForString())) + .property("deleted", codecForBoolean()) + .build("TalerMerchantApi.Instance"); - export interface CashoutInfo { - cashout_id: number; - } - export interface GlobalCashouts { - // Every string represents a cash-out operation ID. - cashouts: GlobalCashoutInfo[]; - } - export interface GlobalCashoutInfo { - cashout_id: number; - username: string; - } +export const codecForExchangeConfig = + (): Codec<TalerExchangeApi.ExchangeVersionResponse> => + buildCodecForObject<TalerExchangeApi.ExchangeVersionResponse>() + .property("version", codecForString()) + .property("name", codecForConstString("taler-exchange")) + .property("implementation", codecOptional(codecForURN())) + .property("currency", codecForString()) + .property("currency_specification", codecForCurrencySpecificiation()) + .property("supported_kyc_requirements", codecForList(codecForString())) + .build("TalerExchangeApi.ExchangeVersionResponse"); - export interface CashoutStatusResponse { - // Amount debited to the internal - // regional currency bank account. - amount_debit: AmountString; +export const codecForExchangeKeys = + (): Codec<TalerExchangeApi.ExchangeKeysResponse> => + buildCodecForObject<TalerExchangeApi.ExchangeKeysResponse>() + .property("version", codecForString()) + .property("base_url", codecForString()) + .property("currency", codecForString()) + .build("TalerExchangeApi.ExchangeKeysResponse"); - // Amount credited to the external bank account. - amount_credit: AmountString; +export const codecForRevenueConfig = (): Codec<TalerRevenueApi.RevenueConfig> => + buildCodecForObject<TalerRevenueApi.RevenueConfig>() + .property("name", codecForConstString("taler-revenue")) + .property("version", codecForString()) + .property("currency", codecForString()) + .property("implementation", codecOptional(codecForString())) + .build("TalerRevenueApi.RevenueConfig"); - // Transaction subject. - subject: string; +export const codecForRevenueIncomingHistory = + (): Codec<TalerRevenueApi.RevenueIncomingHistory> => + buildCodecForObject<TalerRevenueApi.RevenueIncomingHistory>() + .property("credit_account", codecForPaytoString()) + .property( + "incoming_transactions", + codecForList(codecForRevenueIncomingBankTransaction()), + ) + .build("TalerRevenueApi.MerchantIncomingHistory"); - // Time when the cashout was created. - creation_time: Timestamp; - } +export const codecForRevenueIncomingBankTransaction = + (): Codec<TalerRevenueApi.RevenueIncomingBankTransaction> => + buildCodecForObject<TalerRevenueApi.RevenueIncomingBankTransaction>() + .property("amount", codecForAmountString()) + .property("date", codecForTimestamp) + .property("debit_account", codecForPaytoString()) + .property("row_id", codecForNumber()) + .property("subject", codecForString()) + .build("TalerRevenueApi.RevenueIncomingBankTransaction"); - export interface ConversionRatesResponse { - // Exchange rate to buy the local currency from the external one - buy_at_ratio: DecimalNumber; +export const codecForAmlRecords = (): Codec<TalerExchangeApi.AmlRecords> => + buildCodecForObject<TalerExchangeApi.AmlRecords>() + .property("records", codecForList(codecForAmlRecord())) + .build("TalerExchangeApi.AmlRecords"); - // Exchange rate to sell the local currency for the external one - sell_at_ratio: DecimalNumber; +export const codecForAmlRecord = (): Codec<TalerExchangeApi.AmlRecord> => + buildCodecForObject<TalerExchangeApi.AmlRecord>() + .property("current_state", codecForNumber()) + .property("h_payto", codecForString()) + .property("rowid", codecForNumber()) + .property("threshold", codecForAmountString()) + .build("TalerExchangeApi.AmlRecord"); - // Fee to subtract after applying the buy ratio. - buy_in_fee: DecimalNumber; +export const codecForAmlDecisionDetails = + (): Codec<TalerExchangeApi.AmlDecisionDetails> => + buildCodecForObject<TalerExchangeApi.AmlDecisionDetails>() + .property("aml_history", codecForList(codecForAmlDecisionDetail())) + .property("kyc_attributes", codecForList(codecForKycDetail())) + .build("TalerExchangeApi.AmlDecisionDetails"); - // Fee to subtract after applying the sell ratio. - sell_out_fee: DecimalNumber; - } +export const codecForAmlDecisionDetail = + (): Codec<TalerExchangeApi.AmlDecisionDetail> => + buildCodecForObject<TalerExchangeApi.AmlDecisionDetail>() + .property("justification", codecForString()) + .property("new_state", codecForNumber()) + .property("decision_time", codecForTimestamp) + .property("new_threshold", codecForAmountString()) + .property("decider_pub", codecForString()) + .build("TalerExchangeApi.AmlDecisionDetail"); - export enum MonitorTimeframeParam { - hour, - day, - month, - year, - decade, - } +interface KycDetail { + provider_section: string; + attributes?: Object; + collection_time: Timestamp; + expiration_time: Timestamp; +} +export const codecForKycDetail = (): Codec<TalerExchangeApi.KycDetail> => + buildCodecForObject<TalerExchangeApi.KycDetail>() + .property("provider_section", codecForString()) + .property("attributes", codecOptional(codecForAny())) + .property("collection_time", codecForTimestamp) + .property("expiration_time", codecForTimestamp) + .build("TalerExchangeApi.KycDetail"); - export type MonitorResponse = MonitorNoConversion | MonitorWithConversion; +export const codecForAmlDecision = (): Codec<TalerExchangeApi.AmlDecision> => + buildCodecForObject<TalerExchangeApi.AmlDecision>() + .property("justification", codecForString()) + .property("new_threshold", codecForAmountString()) + .property("h_payto", codecForString()) + .property("new_state", codecForNumber()) + .property("officer_sig", codecForString()) + .property("decision_time", codecForTimestamp) + .property("kyc_requirements", codecOptional(codecForList(codecForString()))) + .build("TalerExchangeApi.AmlDecision"); - // Monitoring stats when conversion is not supported - export interface MonitorNoConversion { - type: "no-conversions"; +export const codecForTemplateEditableDetails = + (): Codec<TalerMerchantApi.TemplateEditableDetails> => + buildCodecForObject<TalerMerchantApi.TemplateEditableDetails>() + .property("summary", codecOptional(codecForString())) + .property("currency", codecOptional(codecForString())) + .property("amount", codecOptional(codecForAmountString())) + .build("TemplateEditableDetails"); - // How many payments were made to a Taler exchange by another - // bank account. - talerInCount: number; +export const codecForMerchantReserveCreateConfirmation = + (): Codec<TalerMerchantApi.MerchantReserveCreateConfirmation> => + buildCodecForObject<TalerMerchantApi.MerchantReserveCreateConfirmation>() + .property("accounts", codecForList(codecForExchangeWireAccount())) + .property("reserve_pub", codecForString()) + .build("MerchantReserveCreateConfirmation"); - // Overall volume that has been paid to a Taler - // exchange by another bank account. - talerInVolume: AmountString; +type EddsaSignature = string; +// base32 encoded RSA blinded signature. +type BlindedRsaSignature = string; +type Base32 = string; - // How many payments were made by a Taler exchange to another - // bank account. - talerOutCount: number; +type RsaSignature = string; +type Float = number; +type LibtoolVersion = string; +// The type of a coin's blinded envelope depends on the cipher that is used +// for signing with a denomination key. +type CoinEnvelope = RSACoinEnvelope | CSCoinEnvelope; +// For denomination signatures based on RSA, the planchet is just a blinded +// coin's public EdDSA key. +interface RSACoinEnvelope { + cipher: "RSA" | "RSA+age_restricted"; + rsa_blinded_planchet: string; // Crockford Base32 encoded +} +// For denomination signatures based on Blind Clause-Schnorr, the planchet +// consists of the public nonce and two Curve25519 scalars which are two +// blinded challenges in the Blinded Clause-Schnorr signature scheme. +// See https://taler.net/papers/cs-thesis.pdf for details. +interface CSCoinEnvelope { + cipher: "CS" | "CS+age_restricted"; + cs_nonce: string; // Crockford Base32 encoded + cs_blinded_c0: string; // Crockford Base32 encoded + cs_blinded_c1: string; // Crockford Base32 encoded +} +// Secret for blinding/unblinding. +// An RSA blinding secret, which is basically +// a 256-bit nonce, converted to Crockford Base32. +type DenominationBlindingKeyP = string; - // Overall volume that has been paid by a Taler - // exchange to another bank account. - talerOutVolume: AmountString; - } - // Monitoring stats when conversion is supported - export interface MonitorWithConversion { - type: "with-conversions"; +export * as TalerWireGatewayApi from "../types-taler-wire-gateway.js"; - // How many cashin operations were confirmed by a - // wallet owner. Note: wallet owners - // are NOT required to be customers of the libeufin-bank. - cashinCount: number; +export namespace TalerRevenueApi { + export interface RevenueConfig { + // Name of the API. + name: "taler-revenue"; - // Overall regional currency that has been paid by the regional admin account - // to regional bank accounts to fulfill all the confirmed cashin operations. - cashinRegionalVolume: AmountString; + // libtool-style representation of the Bank protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; - // Overall fiat currency that has been paid to the fiat admin account - // by fiat bank accounts to fulfill all the confirmed cashin operations. - cashinFiatVolume: AmountString; + // Currency used by this gateway. + currency: string; - // How many cashout operations were confirmed. - cashoutCount: number; + // URN of the implementation (needed to interpret 'revision' in version). + // @since v0, may become mandatory in the future. + implementation?: string; + } - // Overall regional currency that has been paid to the regional admin account - // by fiat bank accounts to fulfill all the confirmed cashout operations. - cashoutRegionalVolume: AmountString; + export interface RevenueIncomingHistory { + // Array of incoming transactions. + incoming_transactions: RevenueIncomingBankTransaction[]; - // Overall fiat currency that has been paid by the fiat admin account - // to fiat bank accounts to fulfill all the confirmed cashout operations. - cashoutFiatVolume: AmountString; + // Payto URI to identify the receiver of funds. + // Credit account is shared by all incoming transactions + // as per the nature of the request. + credit_account: string; + } - // How many payments were made to a Taler exchange by another - // bank account. - talerInCount: number; + export interface RevenueIncomingBankTransaction { + // Opaque identifier of the returned record. + row_id: SafeUint64; - // Overall volume that has been paid to a Taler - // exchange by another bank account. - talerInVolume: AmountString; + // Date of the transaction. + date: Timestamp; - // How many payments were made by a Taler exchange to another - // bank account. - talerOutCount: number; + // Amount transferred. + amount: AmountString; - // Overall volume that has been paid by a Taler - // exchange to another bank account. - talerOutVolume: AmountString; - } - export interface TanTransmission { - // Channel of the last successful transmission of the TAN challenge. - tan_channel: TanChannel; + // Payto URI to identify the sender of funds. + debit_account: string; - // Info of the last successful transmission of the TAN challenge. - tan_info: string; + // The wire transfer subject. + subject: string; } +} - export interface Challenge { - // Unique identifier of the challenge to solve to run this protected - // operation. - challenge_id: number; - } +export * as TalerBankIntegrationApi from "../types-taler-bank-integration.js"; - export interface ChallengeSolve { - // The TAN code that solves $CHALLENGE_ID - tan: string; - } +export * as TalerBankConversionApi from "../types-taler-bank-conversion.js"; - export enum TanChannel { - SMS = "sms", - EMAIL = "email", - } -} +export * as TalerCorebankApi from "../types-taler-corebank.js"; export namespace TalerExchangeApi { export enum AmlState { @@ -5383,166 +3722,5 @@ export namespace TalerMerchantApi { } } -export namespace ChallengerApi { - export interface ChallengerTermsOfServiceResponse { - // Name of the service - name: "challenger"; - - // libtool-style representation of the Challenger protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // URN of the implementation (needed to interpret 'revision' in version). - // @since v0, may become mandatory in the future. - implementation?: string; - - // Object; map of keys (names of the fields of the address - // to be entered by the user) to objects with a "regex" (string) - // containing an extended Posix regular expression for allowed - // address field values, and a "hint"/"hint_i18n" giving a - // human-readable explanation to display if the value entered - // by the user does not match the regex. Keys that are not mapped - // to such an object have no restriction on the value provided by - // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration. - restrictions: Record<string, Restriction> | undefined; - - // @since v2. - address_type: "email" | "phone"; - } - - export interface ChallengeSetupResponse { - // Nonce to use when constructing /authorize endpoint. - nonce: string; - } - - export interface Restriction { - regex?: string; - hint?: string; - hint_i18n?: InternationalizedString; - } - - export interface ChallengeStatus { - // indicates if the given address cannot be changed anymore, the - // form should be read-only if set to true. - fix_address: boolean; - - // form values from the previous submission if available, details depend - // on the ADDRESS_TYPE, should be used to pre-populate the form - last_address: Record<string, string> | undefined; - - // number of times the address can still be changed, may or may not be - // shown to the user - changes_left: Integer; - - // is the challenge already solved? - solved: boolean; - - // when we would re-transmit the challenge the next - // time (at the earliest) if requested by the user - // only present if challenge already created - // @since v2 - retransmission_time: Timestamp; - - // how many times might the PIN still be retransmitted - // only present if challenge already created - // @since v2 - pin_transmissions_left: Integer; - - // how many times might the user still try entering the PIN code - // only present if challenge already created - // @since v2 - auth_attempts_left: Integer; - } - - export type ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse; - - export interface ChallengeRedirect { - type: "completed"; - // challenge is completed, use should redirect here - redirect_url: string; - } - - export interface ChallengeCreateResponse { - type: "created"; - // how many more attempts are allowed, might be shown to the user, - // highlighting might be appropriate for low values such as 1 or 2 (the - // form will never be used if the value is zero) - attempts_left: Integer; - - // the address that is being validated, might be shown or not - address: Object; - - // true if we just retransmitted the challenge, false if we sent a - // challenge recently and thus refused to transmit it again this time; - // might make a useful hint to the user - transmitted: boolean; - - // timestamp explaining when we would re-transmit the challenge the next - // time (at the earliest) if requested by the user - retransmission_time: TalerProtocolTimestamp; - } - - export type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse; - - export interface InvalidPinResponse { - type: "pending"; - - // numeric Taler error code, should be shown to indicate the error - // compactly for reporting to developers - ec?: number; - - // human-readable Taler error code, should be shown for the user to - // understand the error - hint: string; - - // how many times is the user still allowed to change the address; - // if 0, the user should not be shown a link to jump to the - // address entry form - addresses_left: Integer; - - // how many times might the PIN still be retransmitted - pin_transmissions_left: Integer; - - // how many times might the user still try entering the PIN code - auth_attempts_left: Integer; - - // if true, the PIN was not even evaluated as the user previously - // exhausted the number of attempts - exhausted: boolean; - - // if true, the PIN was not even evaluated as no challenge was ever - // issued (the user must have skipped the step of providing their - // address first!) - no_challenge: boolean; - } - - export interface ChallengerAuthResponse { - // Token used to authenticate access in /info. - access_token: string; - - // Type of the access token. - token_type: "Bearer"; - - // Amount of time that an access token is valid (in seconds). - expires_in: Integer; - } - - export interface ChallengerInfoResponse { - // Unique ID of the record within Challenger - // (identifies the rowid of the token). - id: Integer; - - // Address that was validated. - // Key-value pairs, details depend on the - // address_type. - address: Object; - - // Type of the address. - address_type: string; - - // How long do we consider the address to be - // valid for this user. - expires: Timestamp; - } -} +// Deprecated alias, for easier refactoring! +export * as ChallengerApi from "../types-taler-challenger.js"; diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts @@ -28,7 +28,7 @@ import { import { Logger } from "./logging.js"; import { TalerErrorCode } from "./taler-error-codes.js"; import { AbsoluteTime, Duration } from "./time.js"; -import { TalerErrorDetail } from "./wallet-types.js"; +import { TalerErrorDetail } from "./types-taler-wallet.js"; const textEncoder = new TextEncoder(); diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts @@ -9,7 +9,6 @@ export * from "./ReserveStatus.js"; export * from "./ReserveTransaction.js"; export { TaskThrottler } from "./TaskThrottler.js"; export * from "./amounts.js"; -export * from "./backup-types.js"; export * from "./bank-api-client.js"; export * from "./base64.js"; export * from "./bitcoin.js"; @@ -53,11 +52,18 @@ export * from "./promises.js"; export * from "./qr.js"; export * from "./rfc3548.js"; export * from "./taler-crypto.js"; -export * from "./taler-types.js"; export * from "./taleruri.js"; export * from "./time.js"; export * from "./timer.js"; export * from "./transaction-test-data.js"; -export * from "./transactions-types.js"; export * from "./url.js"; -export * from "./wallet-types.js"; + +export * from "./types-taler-bank-conversion.js"; +export * from "./types-taler-bank-integration.js"; +export * from "./types-taler-common.js"; +export * from "./types-taler-corebank.js"; +export * from "./types-taler-exchange.js"; +export * from "./types-taler-merchant.js"; +export * from "./types-taler-sync.js"; +export * from "./types-taler-wallet-transactions.js"; +export * from "./types-taler-wallet.js"; diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts @@ -23,12 +23,12 @@ * Imports. */ import { AbsoluteTime } from "./time.js"; -import { TransactionState } from "./transactions-types.js"; +import { TransactionState } from "./types-taler-wallet-transactions.js"; import { ExchangeEntryState, TalerErrorDetail, TransactionIdStr, -} from "./wallet-types.js"; +} from "./types-taler-wallet.js"; export enum NotificationType { BalanceChange = "balance-change", diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts @@ -39,8 +39,9 @@ export type PaytoUri = | PaytoUriTalerBank | PaytoUriBitcoin; -declare const __payto_str: unique symbol; -export type PaytoString = string & { [__payto_str]: true }; +// declare const __payto_str: unique symbol; +// export type PaytoString = string & { [__payto_str]: true }; +export type PaytoString = string; export function codecForPaytoString(): Codec<PaytoString> { return { diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts @@ -30,14 +30,12 @@ import { hmacSha256, hmacSha512 } from "./kdf.js"; import { Logger } from "./logging.js"; import * as nacl from "./nacl-fast.js"; import { secretbox } from "./nacl-fast.js"; +import { TalerProtocolDuration, TalerProtocolTimestamp } from "./time.js"; import { - CoinEnvelope, CoinPublicKeyString, - DenomKeyType, - DenominationPubKey, HashCodeString, -} from "./taler-types.js"; -import { TalerProtocolDuration, TalerProtocolTimestamp } from "./time.js"; +} from "./types-taler-common.js"; +import { CoinEnvelope, DenomKeyType, DenominationPubKey } from "./types-taler-exchange.js"; export type Flavor<T, FlavorT extends string> = T & { _flavor?: `taler.${FlavorT}`; diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts @@ -1,2424 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type and schema definitions and helpers for the core GNU Taler protocol. - * - * 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 { Amounts, codecForAmountString } from "./amounts.js"; -import { - Codec, - buildCodecForObject, - buildCodecForUnion, - codecForAny, - codecForBoolean, - codecForConstString, - codecForList, - codecForMap, - codecForNumber, - codecForString, - codecForStringURL, - codecOptional, -} from "./codec.js"; -import { strcmp } from "./helpers.js"; -import { - CurrencySpecification, - codecForCurrencySpecificiation, - codecForEither, - codecForProduct, -} from "./index.js"; -import { Edx25519PublicKeyEnc } from "./taler-crypto.js"; -import { - TalerProtocolDuration, - TalerProtocolTimestamp, - codecForDuration, - codecForTimestamp, -} from "./time.js"; - -/** - * Denomination as found in the /keys response from the exchange. - */ -export class ExchangeDenomination { - /** - * Value of one coin of the denomination. - */ - value: string; - - /** - * Public signing key of the denomination. - */ - denom_pub: DenominationPubKey; - - /** - * Fee for withdrawing. - */ - fee_withdraw: string; - - /** - * Fee for depositing. - */ - fee_deposit: string; - - /** - * Fee for refreshing. - */ - fee_refresh: string; - - /** - * Fee for refunding. - */ - fee_refund: string; - - /** - * Start date from which withdraw is allowed. - */ - stamp_start: TalerProtocolTimestamp; - - /** - * End date for withdrawing. - */ - stamp_expire_withdraw: TalerProtocolTimestamp; - - /** - * Expiration date after which the exchange can forget about - * the currency. - */ - stamp_expire_legal: TalerProtocolTimestamp; - - /** - * Date after which the coins of this denomination can't be - * deposited anymore. - */ - stamp_expire_deposit: TalerProtocolTimestamp; - - /** - * Signature over the denomination information by the exchange's master - * signing key. - */ - master_sig: string; -} - -/** - * Signature by the auditor that a particular denomination key is audited. - */ -export class AuditorDenomSig { - /** - * Denomination public key's hash. - */ - denom_pub_h: string; - - /** - * The signature. - */ - auditor_sig: string; -} - -/** - * Auditor information as given by the exchange in /keys. - */ -export class ExchangeAuditor { - /** - * Auditor's public key. - */ - auditor_pub: string; - - /** - * Base URL of the auditor. - */ - auditor_url: string; - - /** - * List of signatures for denominations by the auditor. - */ - denomination_keys: AuditorDenomSig[]; -} - -export type ExchangeWithdrawValue = - | ExchangeRsaWithdrawValue - | ExchangeCsWithdrawValue; - -export interface ExchangeRsaWithdrawValue { - cipher: "RSA"; -} - -export interface ExchangeCsWithdrawValue { - cipher: "CS"; - - /** - * CSR R0 value - */ - r_pub_0: string; - - /** - * CSR R1 value - */ - r_pub_1: string; -} - -export interface RecoupRequest { - /** - * Hashed denomination public key of the coin we want to get - * paid back. - */ - denom_pub_hash: string; - - /** - * Signature over the coin public key by the denomination. - * - * The string variant is for the legacy exchange protocol. - */ - denom_sig: UnblindedSignature; - - /** - * Blinding key that was used during withdraw, - * used to prove that we were actually withdrawing the coin. - */ - coin_blind_key_secret: string; - - /** - * Signature of TALER_RecoupRequestPS created with the coin's private key. - */ - coin_sig: string; - - ewv: ExchangeWithdrawValue; -} - -export interface RecoupRefreshRequest { - /** - * Hashed enomination public key of the coin we want to get - * paid back. - */ - denom_pub_hash: string; - - /** - * Signature over the coin public key by the denomination. - * - * The string variant is for the legacy exchange protocol. - */ - denom_sig: UnblindedSignature; - - /** - * Coin's blinding factor. - */ - coin_blind_key_secret: string; - - /** - * Signature of TALER_RecoupRefreshRequestPS created with - * the coin's private key. - */ - coin_sig: string; - - ewv: ExchangeWithdrawValue; -} - -/** - * Response that we get from the exchange for a payback request. - */ -export interface RecoupConfirmation { - /** - * Public key of the reserve that will receive the payback. - */ - reserve_pub?: string; - - /** - * Public key of the old coin that will receive the recoup, - * provided if refreshed was true. - */ - old_coin_pub?: string; -} - -export type UnblindedSignature = RsaUnblindedSignature; - -export interface RsaUnblindedSignature { - cipher: DenomKeyType.Rsa; - rsa_signature: string; -} - -/** - * Deposit permission for a single coin. - */ -export interface CoinDepositPermission { - /** - * Signature by the coin. - */ - coin_sig: string; - - /** - * Public key of the coin being spend. - */ - coin_pub: string; - - /** - * Signature made by the denomination public key. - * - * The string variant is for legacy protocol support. - */ - - ub_sig: UnblindedSignature; - - /** - * The denomination public key associated with this coin. - */ - h_denom: string; - - /** - * The amount that is subtracted from this coin with this payment. - */ - contribution: string; - - /** - * URL of the exchange this coin was withdrawn from. - */ - exchange_url: string; - - minimum_age_sig?: EddsaSignatureString; - - age_commitment?: Edx25519PublicKeyEnc[]; - - h_age_commitment?: string; -} - -/** - * Information about an exchange as stored inside a - * merchant's contract terms. - */ -export interface ExchangeHandle { - // The exchange's base URL. - url: string; - - // Master public key of the exchange. - master_pub: EddsaPublicKeyString; -} - -export interface AuditorHandle { - /** - * Official name of the auditor. - */ - name: string; - - /** - * Master public signing key of the auditor. - */ - auditor_pub: EddsaPublicKeyString; - - /** - * Base URL of the auditor. - */ - url: string; -} - -// Delivery location, loosely modeled as a subset of -// ISO20022's PostalAddress25. -export interface Location { - // Nation with its own government. - country?: string; - - // Identifies a subdivision of a country such as state, region, county. - country_subdivision?: string; - - // Identifies a subdivision within a country sub-division. - district?: string; - - // Name of a built-up area, with defined boundaries, and a local government. - town?: string; - - // Specific location name within the town. - town_location?: string; - - // Identifier consisting of a group of letters and/or numbers that - // is added to a postal address to assist the sorting of mail. - post_code?: string; - - // Name of a street or thoroughfare. - street?: string; - - // Name of the building or house. - building_name?: string; - - // Number that identifies the position of a building on a street. - building_number?: string; - - // Free-form address lines, should not exceed 7 elements. - address_lines?: string[]; -} - -export interface MerchantInfo { - // The merchant's legal name of business. - name: string; - - // Label for a location with the business address of the merchant. - email?: string; - - // Label for a location with the business address of the merchant. - website?: string; - - // An optional base64-encoded product image. - logo?: ImageDataUrl; - - // Label for a location with the business address of the merchant. - address?: Location; - - // Label for a location that denotes the jurisdiction for disputes. - // Some of the typical fields for a location (such as a street address) may be absent. - jurisdiction?: Location; -} - -export interface Tax { - // the name of the tax - name: string; - - // amount paid in tax - tax: AmountString; -} - -export interface Product { - // merchant-internal identifier for the product. - product_id?: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n?: InternationalizedString; - - // The number of units of the product to deliver to the customer. - quantity?: Integer; - - // The unit in which the product is measured (liters, kilograms, packages, etc.) - unit?: string; - - // The price of the product; this is the total price for quantity times unit of this product. - price?: AmountString; - - // An optional base64-encoded product image - image?: ImageDataUrl; - - // a list of taxes paid by the merchant for this product. Can be empty. - taxes?: Tax[]; - - // time indicating when this product should be delivered - delivery_date?: TalerProtocolTimestamp; -} - -export interface InternationalizedString { - [lang_tag: string]: string; -} - -/** - * Contract terms from a merchant. - * FIXME: Add type field! - */ -export interface MerchantContractTerms { - // The hash of the merchant instance's wire details. - h_wire: string; - - // Specifies for how long the wallet should try to get an - // automatic refund for the purchase. If this field is - // present, the wallet should wait for a few seconds after - // the purchase and then automatically attempt to obtain - // a refund. The wallet should probe until "delay" - // after the payment was successful (i.e. via long polling - // or via explicit requests with exponential back-off). - // - // In particular, if the wallet is offline - // at that time, it MUST repeat the request until it gets - // one response from the merchant after the delay has expired. - // If the refund is granted, the wallet MUST automatically - // recover the payment. This is used in case a merchant - // knows that it might be unable to satisfy the contract and - // desires for the wallet to attempt to get the refund without any - // customer interaction. Note that it is NOT an error if the - // merchant does not grant a refund. - auto_refund?: TalerProtocolDuration; - - // Wire transfer method identifier for the wire method associated with h_wire. - // The wallet may only select exchanges via a matching auditor if the - // exchange also supports this wire method. - // The wire transfer fees must be added based on this wire transfer method. - wire_method: string; - - // Human-readable description of the whole purchase. - summary: string; - - // Map from IETF BCP 47 language tags to localized summaries. - summary_i18n?: InternationalizedString; - - // Unique, free-form identifier for the proposal. - // Must be unique within a merchant instance. - // For merchants that do not store proposals in their DB - // before the customer paid for them, the order_id can be used - // by the frontend to restore a proposal from the information - // encoded in it (such as a short product identifier and timestamp). - order_id: string; - - // Total price for the transaction. - // The exchange will subtract deposit fees from that amount - // before transferring it to the merchant. - amount: string; - - // Nonce generated by the wallet and echoed by the merchant - // in this field when the proposal is generated. - nonce: string; - - // After this deadline, the merchant won't accept payments for the contract. - pay_deadline: TalerProtocolTimestamp; - - // More info about the merchant, see below. - merchant: MerchantInfo; - - // Merchant's public key used to sign this proposal; this information - // is typically added by the backend. Note that this can be an ephemeral key. - merchant_pub: string; - - // Time indicating when the order should be delivered. - // May be overwritten by individual products. - delivery_date?: TalerProtocolTimestamp; - - // Delivery location for (all!) products. - delivery_location?: Location; - - // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - exchanges: ExchangeHandle[]; - - // List of products that are part of the purchase (see Product). - products?: Product[]; - - // After this deadline has passed, no refunds will be accepted. - refund_deadline: TalerProtocolTimestamp; - - // Transfer deadline for the exchange. Must be in the - // deposit permissions of coins used to pay for this order. - wire_transfer_deadline: TalerProtocolTimestamp; - - // Time when this contract was generated. - timestamp: TalerProtocolTimestamp; - - // Base URL of the (public!) merchant backend API. - // Must be an absolute URL that ends with a slash. - merchant_base_url: string; - - // URL that will show that the order was successful after - // it has been paid for. Optional, but either fulfillment_url - // or fulfillment_message must be specified in every - // contract terms. - // - // If a non-unique fulfillment URL is used, a customer can only - // buy the order once and will be redirected to a previous purchase - // when trying to buy an order with the same fulfillment URL a second - // time. This is useful for digital goods that a customer only needs - // to buy once but should be able to repeatedly download. - // - // For orders where the customer is expected to be able to make - // repeated purchases (for equivalent goods), the fulfillment URL - // should be made unique for every order. The easiest way to do - // this is to include a unique order ID in the fulfillment URL. - // - // When POSTing to the merchant, the placeholder text "${ORDER_ID}" - // is be replaced with the actual order ID (useful if the - // order ID is generated server-side and needs to be - // in the URL). Note that this placeholder can only be used once. - // Front-ends may use other means to generate a unique fulfillment URL. - fulfillment_url?: string; - - // URL where the same contract could be ordered again (if - // available). Returned also at the public order endpoint - // for people other than the actual buyer (hence public, - // in case order IDs are guessable). - public_reorder_url?: string; - - // Message shown to the customer after paying for the order. - // Either fulfillment_url or fulfillment_message must be specified. - fulfillment_message?: string; - - // Map from IETF BCP 47 language tags to localized fulfillment - // messages. - fulfillment_message_i18n?: InternationalizedString; - - // Maximum total deposit fee accepted by the merchant for this contract. - // Overrides defaults of the merchant instance. - max_fee: string; - - // Extra data that is only interpreted by the merchant frontend. - // Useful when the merchant needs to store extra information on a - // contract without storing it separately in their database. - // Must really be an Object (not a string, integer, float or array). - extra?: any; - - // Minimum age the buyer must have (in years). Default is 0. - // This value is at least as large as the maximum over all - // minimum age requirements of the products in this contract. - // It might also be set independent of any product, due to - // legal requirements. - minimum_age?: Integer; -} - -/** - * Refund permission in the format that the merchant gives it to us. - */ -export interface MerchantAbortPayRefundDetails { - /** - * Amount to be refunded. - */ - refund_amount: string; - - /** - * Fee for the refund. - */ - refund_fee: string; - - /** - * Public key of the coin being refunded. - */ - coin_pub: string; - - /** - * Refund transaction ID between merchant and exchange. - */ - rtransaction_id: number; - - /** - * Exchange's key used for the signature. - */ - exchange_pub?: string; - - /** - * Exchange's signature to confirm the refund. - */ - exchange_sig?: string; - - /** - * Error replay from the exchange (if any). - */ - exchange_reply?: any; - - /** - * Error code from the exchange (if any). - */ - exchange_code?: number; - - /** - * HTTP status code of the exchange's response - * to the merchant's refund request. - */ - exchange_http_status: number; -} - -/** - * Planchet detail sent to the merchant. - */ -export interface TipPlanchetDetail { - /** - * Hashed denomination public key. - */ - denom_pub_hash: string; - - /** - * Coin's blinded public key. - */ - coin_ev: CoinEnvelope; -} - -/** - * 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. - */ -export interface MerchantBlindSigWrapperV1 { - /** - * Reserve signature. - */ - blind_sig: string; -} - -/** - * Response of the merchant - * to the TipPickupRequest. - */ -export interface MerchantTipResponseV1 { - /** - * The order of the signatures matches the planchets list. - */ - blind_sigs: MerchantBlindSigWrapperV1[]; -} - -export interface MerchantBlindSigWrapperV2 { - blind_sig: BlindedDenominationSignature; -} - -/** - * Response of the merchant - * to the TipPickupRequest. - */ -export interface MerchantTipResponseV2 { - /** - * The order of the signatures matches the planchets list. - */ - blind_sigs: MerchantBlindSigWrapperV2[]; -} - -/** - * Element of the payback list that the - * exchange gives us in /keys. - */ -export class Recoup { - /** - * The hash of the denomination public key for which the payback is offered. - */ - h_denom_pub: string; -} - -/** - * Structure of one exchange signing key in the /keys response. - */ -export class ExchangeSignKeyJson { - stamp_start: TalerProtocolTimestamp; - stamp_expire: TalerProtocolTimestamp; - stamp_end: TalerProtocolTimestamp; - key: EddsaPublicKeyString; - master_sig: EddsaSignatureString; -} - -/** - * Structure that the exchange gives us in /keys. - */ -export class ExchangeKeysJson { - /** - * Canonical, public base URL of the exchange. - */ - base_url: string; - - currency: string; - - currency_specification?: CurrencySpecification; - - /** - * The exchange's master public key. - */ - master_public_key: string; - - /** - * The list of auditors (partially) auditing the exchange. - */ - auditors: ExchangeAuditor[]; - - /** - * Timestamp when this response was issued. - */ - list_issue_date: TalerProtocolTimestamp; - - /** - * List of revoked denominations. - */ - recoup?: Recoup[]; - - /** - * Short-lived signing keys used to sign online - * responses. - */ - signkeys: ExchangeSignKeyJson[]; - - /** - * Protocol version. - */ - version: string; - - reserve_closing_delay: TalerProtocolDuration; - - global_fees: GlobalFees[]; - - accounts: ExchangeWireAccount[]; - - wire_fees: { [methodName: string]: WireFeesJson[] }; - - denominations: DenomGroup[]; -} - -export type DenomGroup = - | DenomGroupRsa - | DenomGroupCs - | DenomGroupRsaAgeRestricted - | DenomGroupCsAgeRestricted; - -export interface DenomGroupCommon { - // How much are coins of this denomination worth? - value: AmountString; - - // Fee charged by the exchange for withdrawing a coin of this denomination. - fee_withdraw: AmountString; - - // Fee charged by the exchange for depositing a coin of this denomination. - fee_deposit: AmountString; - - // Fee charged by the exchange for refreshing a coin of this denomination. - fee_refresh: AmountString; - - // Fee charged by the exchange for refunding a coin of this denomination. - fee_refund: AmountString; - - // XOR of all the SHA-512 hash values of the denominations' public keys - // in this group. Note that for hashing, the binary format of the - // public keys is used, and not their base32 encoding. - hash: HashCodeString; -} - -export interface DenomCommon { - // Signature of TALER_DenominationKeyValidityPS. - master_sig: EddsaSignatureString; - - // When does the denomination key become valid? - stamp_start: TalerProtocolTimestamp; - - // When is it no longer possible to deposit coins - // of this denomination? - stamp_expire_withdraw: TalerProtocolTimestamp; - - // Timestamp indicating by when legal disputes relating to these coins must - // be settled, as the exchange will afterwards destroy its evidence relating to - // transactions involving this coin. - stamp_expire_legal: TalerProtocolTimestamp; - - stamp_expire_deposit: TalerProtocolTimestamp; - - // Set to 'true' if the exchange somehow "lost" - // the private key. The denomination was not - // necessarily revoked, but still cannot be used - // to withdraw coins at this time (theoretically, - // the private key could be recovered in the - // future; coins signed with the private key - // remain valid). - lost?: boolean; -} - -export type RsaPublicKeySring = string; -export type AgeMask = number; -export type ImageDataUrl = string; - -/** - * 32-byte value representing a point on Curve25519. - */ -export type Cs25519Point = string; - -export interface DenomGroupRsa extends DenomGroupCommon { - cipher: "RSA"; - - denoms: ({ - rsa_pub: RsaPublicKeySring; - } & DenomCommon)[]; -} - -export interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { - cipher: "RSA+age_restricted"; - age_mask: AgeMask; - - denoms: ({ - rsa_pub: RsaPublicKeySring; - } & DenomCommon)[]; -} - -export interface DenomGroupCs extends DenomGroupCommon { - cipher: "CS"; - age_mask: AgeMask; - - denoms: ({ - cs_pub: Cs25519Point; - } & DenomCommon)[]; -} - -export interface DenomGroupCsAgeRestricted extends DenomGroupCommon { - cipher: "CS+age_restricted"; - age_mask: AgeMask; - - denoms: ({ - cs_pub: Cs25519Point; - } & DenomCommon)[]; -} - -export interface GlobalFees { - // What date (inclusive) does these fees go into effect? - start_date: TalerProtocolTimestamp; - - // What date (exclusive) does this fees stop going into effect? - end_date: TalerProtocolTimestamp; - - // Account history fee, charged when a user wants to - // obtain a reserve/account history. - history_fee: AmountString; - - // Annual fee charged for having an open account at the - // exchange. Charged to the account. If the account - // balance is insufficient to cover this fee, the account - // is automatically deleted/closed. (Note that the exchange - // will keep the account history around for longer for - // regulatory reasons.) - account_fee: AmountString; - - // Purse fee, charged only if a purse is abandoned - // and was not covered by the account limit. - purse_fee: AmountString; - - // How long will the exchange preserve the account history? - // After an account was deleted/closed, the exchange will - // retain the account history for legal reasons until this time. - history_expiration: TalerProtocolDuration; - - // Non-negative number of concurrent purses that any - // account holder is allowed to create without having - // to pay the purse_fee. - purse_account_limit: number; - - // How long does an exchange keep a purse around after a purse - // has expired (or been successfully merged)? A 'GET' request - // for a purse will succeed until the purse expiration time - // plus this value. - purse_timeout: TalerProtocolDuration; - - // Signature of TALER_GlobalFeesPS. - master_sig: string; -} -/** - * Wire fees as announced by the exchange. - */ -export class WireFeesJson { - /** - * Cost of a wire transfer. - */ - wire_fee: string; - - /** - * Cost of clising a reserve. - */ - closing_fee: string; - - /** - * Signature made with the exchange's master key. - */ - sig: string; - - /** - * Date from which the fee applies. - */ - start_date: TalerProtocolTimestamp; - - /** - * Data after which the fee doesn't apply anymore. - */ - end_date: TalerProtocolTimestamp; -} - -/** - * Proposal returned from the contract URL. - */ -export class Proposal { - /** - * Contract terms for the propoal. - * Raw, un-decoded JSON object. - */ - contract_terms: any; - - /** - * Signature over contract, made by the merchant. The public key used for signing - * must be contract_terms.merchant_pub. - */ - sig: string; -} - -/** - * Response from the internal merchant API. - */ -export class CheckPaymentResponse { - order_status: string; - refunded: boolean | undefined; - refunded_amount: string | undefined; - contract_terms: any | undefined; - taler_pay_uri: string | undefined; - contract_url: string | undefined; -} - -/** - * Response from the bank. - */ -export class WithdrawOperationStatusResponse { - status: "selected" | "aborted" | "confirmed" | "pending"; - - selection_done: boolean; - - transfer_done: boolean; - - aborted: boolean; - - amount: string | undefined; - - sender_wire?: string; - - suggested_exchange?: string; - - confirm_transfer_url?: string; - - wire_types: string[]; -} - -/** - * Response from the merchant. - */ -export class RewardPickupGetResponse { - reward_amount: string; - - exchange_url: string; - - next_url?: string; - - expiration: TalerProtocolTimestamp; -} - -export enum DenomKeyType { - Rsa = "RSA", - ClauseSchnorr = "CS", -} - -export namespace DenomKeyType { - export function toIntTag(t: DenomKeyType): number { - switch (t) { - case DenomKeyType.Rsa: - return 1; - case DenomKeyType.ClauseSchnorr: - return 2; - } - } -} - -export interface RsaBlindedDenominationSignature { - cipher: DenomKeyType.Rsa; - blinded_rsa_signature: string; -} - -export interface CSBlindedDenominationSignature { - cipher: DenomKeyType.ClauseSchnorr; -} - -export type BlindedDenominationSignature = - | RsaBlindedDenominationSignature - | CSBlindedDenominationSignature; - -export const codecForRsaBlindedDenominationSignature = () => - buildCodecForObject<RsaBlindedDenominationSignature>() - .property("cipher", codecForConstString(DenomKeyType.Rsa)) - .property("blinded_rsa_signature", codecForString()) - .build("RsaBlindedDenominationSignature"); - -export const codecForBlindedDenominationSignature = () => - buildCodecForUnion<BlindedDenominationSignature>() - .discriminateOn("cipher") - .alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature()) - .build("BlindedDenominationSignature"); - -export class ExchangeWithdrawResponse { - ev_sig: BlindedDenominationSignature; -} - -export class ExchangeWithdrawBatchResponse { - ev_sigs: ExchangeWithdrawResponse[]; -} - -export interface MerchantPayResponse { - sig: string; - pos_confirmation?: string; -} - -export interface ExchangeMeltRequest { - coin_pub: CoinPublicKeyString; - confirm_sig: EddsaSignatureString; - denom_pub_hash: HashCodeString; - denom_sig: UnblindedSignature; - rc: string; - value_with_fee: AmountString; - age_commitment_hash?: HashCodeString; -} - -export interface ExchangeMeltResponse { - /** - * Which of the kappa indices does the client not have to reveal. - */ - noreveal_index: number; - - /** - * Signature of TALER_RefreshMeltConfirmationPS whereby the exchange - * affirms the successful melt and confirming the noreveal_index - */ - exchange_sig: EddsaSignatureString; - - /* - * public EdDSA key of the exchange that was used to generate the signature. - * Should match one of the exchange's signing keys from /keys. Again given - * explicitly as the client might otherwise be confused by clock skew as to - * which signing key was used. - */ - exchange_pub: EddsaPublicKeyString; - - /* - * Base URL to use for operations on the refresh context - * (so the reveal operation). If not given, - * the base URL is the same as the one used for this request. - * Can be used if the base URL for /refreshes/ differs from that - * for /coins/, i.e. for load balancing. Clients SHOULD - * respect the refresh_base_url if provided. Any HTTP server - * belonging to an exchange MUST generate a 307 or 308 redirection - * to the correct base URL should a client uses the wrong base - * URL, or if the base URL has changed since the melt. - * - * When melting the same coin twice (technically allowed - * as the response might have been lost on the network), - * the exchange may return different values for the refresh_base_url. - */ - refresh_base_url?: string; -} - -export interface ExchangeRevealItem { - ev_sig: BlindedDenominationSignature; -} - -export interface ExchangeRevealResponse { - // List of the exchange's blinded RSA signatures on the new coins. - ev_sigs: ExchangeRevealItem[]; -} - -interface MerchantOrderStatusPaid { - // Was the payment refunded (even partially, via refund or abort)? - refunded: boolean; - - // Is any amount of the refund still waiting to be picked up (even partially)? - refund_pending: boolean; - - // Amount that was refunded in total. - refund_amount: AmountString; - - // Amount that already taken by the wallet. - refund_taken: AmountString; -} - -interface MerchantOrderRefundResponse { - /** - * Amount that was refunded in total. - */ - refund_amount: AmountString; - - /** - * Successful refunds for this payment, empty array for none. - */ - refunds: MerchantCoinRefundStatus[]; - - /** - * Public key of the merchant. - */ - merchant_pub: EddsaPublicKeyString; -} - -export type MerchantCoinRefundStatus = - | MerchantCoinRefundSuccessStatus - | MerchantCoinRefundFailureStatus; - -export interface MerchantCoinRefundSuccessStatus { - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // the EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund - exchange_sig: EddsaSignatureString; - - // public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; - - // Refund transaction ID. - rtransaction_id: number; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKeyString; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; - - execution_time: TalerProtocolTimestamp; -} - -export interface MerchantCoinRefundFailureStatus { - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: number; - - // Taler error code from the exchange reply, if available. - exchange_code?: number; - - // If available, HTTP reply from the exchange. - exchange_reply?: any; - - // Refund transaction ID. - rtransaction_id: number; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKeyString; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; - - execution_time: TalerProtocolTimestamp; -} - -export interface MerchantOrderStatusUnpaid { - /** - * URI that the wallet must process to complete the payment. - */ - taler_pay_uri: string; - - /** - * Alternative order ID which was paid for already in the same session. - * - * Only given if the same product was purchased before in the same session. - */ - already_paid_order_id?: string; -} - -/** - * Response body for the following endpoint: - * - * POST {talerBankIntegrationApi}/withdrawal-operation/{wopid} - */ -export interface BankWithdrawalOperationPostResponse { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: "selected" | "aborted" | "confirmed" | "pending"; - - // URL that the user needs to navigate to in order to - // complete some final confirmation (e.g. 2FA). - // - // Only applicable when status is selected or pending. - // It may contain withdrawal operation id - confirm_transfer_url?: string; - - // Deprecated field use status instead - // The transfer has been confirmed and registered by the bank. - // Does not guarantee that the funds have arrived at the exchange already. - transfer_done: boolean; -} - -export const codeForBankWithdrawalOperationPostResponse = - (): Codec<BankWithdrawalOperationPostResponse> => - buildCodecForObject<BankWithdrawalOperationPostResponse>() - .property( - "status", - codecForEither( - codecForConstString("selected"), - codecForConstString("confirmed"), - codecForConstString("aborted"), - codecForConstString("pending"), - ), - ) - .property("confirm_transfer_url", codecOptional(codecForString())) - .property("transfer_done", codecForBoolean()) - .build("BankWithdrawalOperationPostResponse"); - -export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; - -export interface RsaDenominationPubKey { - readonly cipher: DenomKeyType.Rsa; - readonly rsa_public_key: string; - readonly age_mask: number; -} - -export interface CsDenominationPubKey { - readonly cipher: DenomKeyType.ClauseSchnorr; - readonly age_mask: number; - readonly cs_public_key: string; -} - -export namespace DenominationPubKey { - export function cmp( - p1: DenominationPubKey, - p2: DenominationPubKey, - ): -1 | 0 | 1 { - if (p1.cipher < p2.cipher) { - return -1; - } else if (p1.cipher > p2.cipher) { - return +1; - } else if ( - p1.cipher === DenomKeyType.Rsa && - p2.cipher === DenomKeyType.Rsa - ) { - if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { - return -1; - } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { - return 1; - } - return strcmp(p1.rsa_public_key, p2.rsa_public_key); - } else if ( - p1.cipher === DenomKeyType.ClauseSchnorr && - p2.cipher === DenomKeyType.ClauseSchnorr - ) { - if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { - return -1; - } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { - return 1; - } - return strcmp(p1.cs_public_key, p2.cs_public_key); - } else { - throw Error("unsupported cipher"); - } - } -} - -export const codecForRsaDenominationPubKey = () => - buildCodecForObject<RsaDenominationPubKey>() - .property("cipher", codecForConstString(DenomKeyType.Rsa)) - .property("rsa_public_key", codecForString()) - .property("age_mask", codecForNumber()) - .build("DenominationPubKey"); - -export const codecForCsDenominationPubKey = () => - buildCodecForObject<CsDenominationPubKey>() - .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr)) - .property("cs_public_key", codecForString()) - .property("age_mask", codecForNumber()) - .build("CsDenominationPubKey"); - -export const codecForDenominationPubKey = () => - buildCodecForUnion<DenominationPubKey>() - .discriminateOn("cipher") - .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey()) - .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) - .build("DenominationPubKey"); - -export type LitAmountString = `${string}:${number}`; - -declare const __amount_str: unique symbol; -export type AmountString = - | (string & { [__amount_str]: true }) - | LitAmountString; -// export type AmountString = string; -export type Base32String = string; -export type EddsaSignatureString = string; -export type EddsaPublicKeyString = string; -export type EddsaPrivateKeyString = string; -export type CoinPublicKeyString = string; - -export const codecForDenomination = (): Codec<ExchangeDenomination> => - buildCodecForObject<ExchangeDenomination>() - .property("value", codecForString()) - .property("denom_pub", codecForDenominationPubKey()) - .property("fee_withdraw", codecForString()) - .property("fee_deposit", codecForString()) - .property("fee_refresh", codecForString()) - .property("fee_refund", codecForString()) - .property("stamp_start", codecForTimestamp) - .property("stamp_expire_withdraw", codecForTimestamp) - .property("stamp_expire_legal", codecForTimestamp) - .property("stamp_expire_deposit", codecForTimestamp) - .property("master_sig", codecForString()) - .build("Denomination"); - -export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => - buildCodecForObject<AuditorDenomSig>() - .property("denom_pub_h", codecForString()) - .property("auditor_sig", codecForString()) - .build("AuditorDenomSig"); - -export const codecForAuditor = (): Codec<ExchangeAuditor> => - buildCodecForObject<ExchangeAuditor>() - .property("auditor_pub", codecForString()) - .property("auditor_url", codecForString()) - .property("denomination_keys", codecForList(codecForAuditorDenomSig())) - .build("Auditor"); - -export const codecForExchangeHandle = (): Codec<ExchangeHandle> => - buildCodecForObject<ExchangeHandle>() - .property("master_pub", codecForString()) - .property("url", codecForString()) - .build("ExchangeHandle"); - -export const codecForAuditorHandle = (): Codec<AuditorHandle> => - buildCodecForObject<AuditorHandle>() - .property("name", codecForString()) - .property("auditor_pub", codecForString()) - .property("url", codecForString()) - .build("AuditorHandle"); - -export const codecForLocation = (): Codec<Location> => - buildCodecForObject<Location>() - .property("country", codecOptional(codecForString())) - .property("country_subdivision", codecOptional(codecForString())) - .property("building_name", codecOptional(codecForString())) - .property("building_number", codecOptional(codecForString())) - .property("district", codecOptional(codecForString())) - .property("street", codecOptional(codecForString())) - .property("post_code", codecOptional(codecForString())) - .property("town", codecOptional(codecForString())) - .property("town_location", codecOptional(codecForString())) - .property("address_lines", codecOptional(codecForList(codecForString()))) - .build("Location"); - -export const codecForMerchantInfo = (): Codec<MerchantInfo> => - buildCodecForObject<MerchantInfo>() - .property("name", codecForString()) - .property("address", codecOptional(codecForLocation())) - .property("jurisdiction", codecOptional(codecForLocation())) - .build("MerchantInfo"); - -export const codecForInternationalizedString = - (): Codec<InternationalizedString> => codecForMap(codecForString()); - -export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> => - buildCodecForObject<MerchantContractTerms>() - .property("order_id", codecForString()) - .property("fulfillment_url", codecOptional(codecForString())) - .property("fulfillment_message", codecOptional(codecForString())) - .property( - "fulfillment_message_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("merchant_base_url", codecForString()) - .property("h_wire", codecForString()) - .property("auto_refund", codecOptional(codecForDuration)) - .property("wire_method", codecForString()) - .property("summary", codecForString()) - .property("summary_i18n", codecOptional(codecForInternationalizedString())) - .property("nonce", codecForString()) - .property("amount", codecForAmountString()) - .property("pay_deadline", codecForTimestamp) - .property("refund_deadline", codecForTimestamp) - .property("wire_transfer_deadline", codecForTimestamp) - .property("timestamp", codecForTimestamp) - .property("delivery_location", codecOptional(codecForLocation())) - .property("delivery_date", codecOptional(codecForTimestamp)) - .property("max_fee", codecForAmountString()) - .property("merchant", codecForMerchantInfo()) - .property("merchant_pub", codecForString()) - .property("exchanges", codecForList(codecForExchangeHandle())) - .property("products", codecOptional(codecForList(codecForProduct()))) - .property("extra", codecForAny()) - .property("minimum_age", codecOptional(codecForNumber())) - .build("MerchantContractTerms"); - -export const codecForPeerContractTerms = (): Codec<PeerContractTerms> => - buildCodecForObject<PeerContractTerms>() - .property("summary", codecForString()) - .property("amount", codecForAmountString()) - .property("purse_expiration", codecForTimestamp) - .build("PeerContractTerms"); - -export const codecForMerchantRefundPermission = - (): Codec<MerchantAbortPayRefundDetails> => - buildCodecForObject<MerchantAbortPayRefundDetails>() - .property("refund_amount", codecForAmountString()) - .property("refund_fee", codecForAmountString()) - .property("coin_pub", codecForString()) - .property("rtransaction_id", codecForNumber()) - .property("exchange_http_status", codecForNumber()) - .property("exchange_code", codecOptional(codecForNumber())) - .property("exchange_reply", codecOptional(codecForAny())) - .property("exchange_sig", codecOptional(codecForString())) - .property("exchange_pub", codecOptional(codecForString())) - .build("MerchantRefundPermission"); - -export const codecForBlindSigWrapperV2 = (): Codec<MerchantBlindSigWrapperV2> => - buildCodecForObject<MerchantBlindSigWrapperV2>() - .property("blind_sig", codecForBlindedDenominationSignature()) - .build("MerchantBlindSigWrapperV2"); - -export const codecForMerchantTipResponseV2 = (): Codec<MerchantTipResponseV2> => - buildCodecForObject<MerchantTipResponseV2>() - .property("blind_sigs", codecForList(codecForBlindSigWrapperV2())) - .build("MerchantTipResponseV2"); - -export const codecForRecoup = (): Codec<Recoup> => - buildCodecForObject<Recoup>() - .property("h_denom_pub", codecForString()) - .build("Recoup"); - -export const codecForExchangeSigningKey = (): Codec<ExchangeSignKeyJson> => - buildCodecForObject<ExchangeSignKeyJson>() - .property("key", codecForString()) - .property("master_sig", codecForString()) - .property("stamp_end", codecForTimestamp) - .property("stamp_start", codecForTimestamp) - .property("stamp_expire", codecForTimestamp) - .build("ExchangeSignKeyJson"); - -export const codecForGlobalFees = (): Codec<GlobalFees> => - buildCodecForObject<GlobalFees>() - .property("start_date", codecForTimestamp) - .property("end_date", codecForTimestamp) - .property("history_fee", codecForAmountString()) - .property("account_fee", codecForAmountString()) - .property("purse_fee", codecForAmountString()) - .property("history_expiration", codecForDuration) - .property("purse_account_limit", codecForNumber()) - .property("purse_timeout", codecForDuration) - .property("master_sig", codecForString()) - .build("GlobalFees"); - -// FIXME: Validate properly! -export const codecForNgDenominations: Codec<DenomGroup> = codecForAny(); - -export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => - buildCodecForObject<ExchangeKeysJson>() - .property("base_url", codecForString()) - .property("currency", codecForString()) - .property("currency_specification", codecOptional(codecForCurrencySpecificiation())) - .property("master_public_key", codecForString()) - .property("auditors", codecForList(codecForAuditor())) - .property("list_issue_date", codecForTimestamp) - .property("recoup", codecOptional(codecForList(codecForRecoup()))) - .property("signkeys", codecForList(codecForExchangeSigningKey())) - .property("version", codecForString()) - .property("reserve_closing_delay", codecForDuration) - .property("global_fees", codecForList(codecForGlobalFees())) - .property("accounts", codecForList(codecForExchangeWireAccount())) - .property("wire_fees", codecForMap(codecForList(codecForWireFeesJson()))) - .property("denominations", codecForList(codecForNgDenominations)) - .build("ExchangeKeysJson"); - -export const codecForWireFeesJson = (): Codec<WireFeesJson> => - buildCodecForObject<WireFeesJson>() - .property("wire_fee", codecForString()) - .property("closing_fee", codecForString()) - .property("sig", codecForString()) - .property("start_date", codecForTimestamp) - .property("end_date", codecForTimestamp) - .build("WireFeesJson"); - -export const codecForProposal = (): Codec<Proposal> => - buildCodecForObject<Proposal>() - .property("contract_terms", codecForAny()) - .property("sig", codecForString()) - .build("Proposal"); - -export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> => - buildCodecForObject<CheckPaymentResponse>() - .property("order_status", codecForString()) - .property("refunded", codecOptional(codecForBoolean())) - .property("refunded_amount", codecOptional(codecForString())) - .property("contract_terms", codecOptional(codecForAny())) - .property("taler_pay_uri", codecOptional(codecForString())) - .property("contract_url", codecOptional(codecForString())) - .build("CheckPaymentResponse"); - -export const codecForWithdrawOperationStatusResponse = - (): Codec<WithdrawOperationStatusResponse> => - buildCodecForObject<WithdrawOperationStatusResponse>() - .property( - "status", - codecForEither( - codecForConstString("selected"), - codecForConstString("confirmed"), - codecForConstString("aborted"), - codecForConstString("pending"), - ), - ) - .property("selection_done", codecForBoolean()) - .property("transfer_done", codecForBoolean()) - .property("aborted", codecForBoolean()) - .property("amount", codecOptional(codecForString())) - .property("sender_wire", codecOptional(codecForString())) - .property("suggested_exchange", codecOptional(codecForString())) - .property("confirm_transfer_url", codecOptional(codecForString())) - .property("wire_types", codecForList(codecForString())) - .build("WithdrawOperationStatusResponse"); - -export const codecForRewardPickupGetResponse = - (): Codec<RewardPickupGetResponse> => - buildCodecForObject<RewardPickupGetResponse>() - .property("reward_amount", codecForString()) - .property("exchange_url", codecForString()) - .property("next_url", codecOptional(codecForString())) - .property("expiration", codecForTimestamp) - .build("TipPickupGetResponse"); - -export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> => - buildCodecForObject<RecoupConfirmation>() - .property("reserve_pub", codecOptional(codecForString())) - .property("old_coin_pub", codecOptional(codecForString())) - .build("RecoupConfirmation"); - -export const codecForWithdrawResponse = (): Codec<ExchangeWithdrawResponse> => - buildCodecForObject<ExchangeWithdrawResponse>() - .property("ev_sig", codecForBlindedDenominationSignature()) - .build("WithdrawResponse"); - -export const codecForExchangeWithdrawBatchResponse = - (): Codec<ExchangeWithdrawBatchResponse> => - buildCodecForObject<ExchangeWithdrawBatchResponse>() - .property("ev_sigs", codecForList(codecForWithdrawResponse())) - .build("WithdrawBatchResponse"); - -export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => - buildCodecForObject<MerchantPayResponse>() - .property("sig", codecForString()) - .property("pos_confirmation", codecOptional(codecForString())) - .build("MerchantPayResponse"); - -export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> => - buildCodecForObject<ExchangeMeltResponse>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("noreveal_index", codecForNumber()) - .property("refresh_base_url", codecOptional(codecForString())) - .build("ExchangeMeltResponse"); - -export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> => - buildCodecForObject<ExchangeRevealItem>() - .property("ev_sig", codecForBlindedDenominationSignature()) - .build("ExchangeRevealItem"); - -export const codecForExchangeRevealResponse = - (): Codec<ExchangeRevealResponse> => - buildCodecForObject<ExchangeRevealResponse>() - .property("ev_sigs", codecForList(codecForExchangeRevealItem())) - .build("ExchangeRevealResponse"); - -export const codecForMerchantOrderStatusPaid = - (): Codec<MerchantOrderStatusPaid> => - buildCodecForObject<MerchantOrderStatusPaid>() - .property("refund_amount", codecForAmountString()) - .property("refund_taken", codecForAmountString()) - .property("refund_pending", codecForBoolean()) - .property("refunded", codecForBoolean()) - .build("MerchantOrderStatusPaid"); - -export const codecForMerchantOrderStatusUnpaid = - (): Codec<MerchantOrderStatusUnpaid> => - buildCodecForObject<MerchantOrderStatusUnpaid>() - .property("taler_pay_uri", codecForString()) - .property("already_paid_order_id", codecOptional(codecForString())) - .build("MerchantOrderStatusUnpaid"); - -export interface AbortRequest { - // hash of the order's contract terms (this is used to authenticate the - // wallet/customer in case $ORDER_ID is guessable). - h_contract: string; - - // List of coins the wallet would like to see refunds for. - // (Should be limited to the coins for which the original - // payment succeeded, as far as the wallet knows.) - coins: AbortingCoin[]; -} - -export interface AbortingCoin { - // Public key of a coin for which the wallet is requesting an abort-related refund. - coin_pub: EddsaPublicKeyString; - - // The amount to be refunded (matches the original contribution) - contribution: AmountString; - - // URL of the exchange this coin was withdrawn from. - exchange_url: string; -} - -export interface AbortResponse { - // List of refund responses about the coins that the wallet - // requested an abort for. In the same order as the 'coins' - // from the original request. - // The rtransaction_id is implied to be 0. - refunds: MerchantAbortPayRefundStatus[]; -} - -export type MerchantAbortPayRefundStatus = - | MerchantAbortPayRefundSuccessStatus - | MerchantAbortPayRefundFailureStatus; - -// Details about why a refund failed. -export interface MerchantAbortPayRefundFailureStatus { - // Used as tag for the sum type RefundStatus sum type. - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: number; - - // Taler error code from the exchange reply, if available. - exchange_code?: number; - - // If available, HTTP reply from the exchange. - exchange_reply?: unknown; -} - -// Additional details needed to verify the refund confirmation signature -// (h_contract_terms and merchant_pub) are already known -// to the wallet and thus not included. -export interface MerchantAbortPayRefundSuccessStatus { - // Used as tag for the sum type MerchantCoinRefundStatus sum type. - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // the EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund - exchange_sig: string; - - // public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: string; -} - -export interface FutureKeysResponse { - future_denoms: any[]; - - future_signkeys: any[]; - - master_pub: string; - - denom_secmod_public_key: string; - - // Public key of the signkey security module. - signkey_secmod_public_key: string; -} - -export const codecForKeysManagementResponse = (): Codec<FutureKeysResponse> => - buildCodecForObject<FutureKeysResponse>() - .property("master_pub", codecForString()) - .property("future_signkeys", codecForList(codecForAny())) - .property("future_denoms", codecForList(codecForAny())) - .property("denom_secmod_public_key", codecForAny()) - .property("signkey_secmod_public_key", codecForAny()) - .build("FutureKeysResponse"); - -export interface MerchantConfigResponse { - currency: string; - name: string; - version: string; -} - -export const codecForMerchantConfigResponse = - (): Codec<MerchantConfigResponse> => - buildCodecForObject<MerchantConfigResponse>() - .property("currency", codecForString()) - .property("name", codecForString()) - .property("version", codecForString()) - .build("MerchantConfigResponse"); - -export enum ExchangeProtocolVersion { - /** - * Current version supported by the wallet. - */ - V12 = 12, -} - -export enum MerchantProtocolVersion { - /** - * Current version supported by the wallet. - */ - V3 = 3, -} - -export type CoinEnvelope = CoinEnvelopeRsa | CoinEnvelopeCs; - -export interface CoinEnvelopeRsa { - cipher: DenomKeyType.Rsa; - rsa_blinded_planchet: string; -} - -export interface CoinEnvelopeCs { - cipher: DenomKeyType.ClauseSchnorr; - // FIXME: add remaining fields -} - -export type HashCodeString = string; - -export interface ExchangeWithdrawRequest { - denom_pub_hash: HashCodeString; - reserve_sig: EddsaSignatureString; - coin_ev: CoinEnvelope; -} - -export interface ExchangeBatchWithdrawRequest { - planchets: ExchangeWithdrawRequest[]; -} - -export interface ExchangeRefreshRevealRequest { - new_denoms_h: HashCodeString[]; - coin_evs: CoinEnvelope[]; - /** - * kappa - 1 transfer private keys (ephemeral ECDHE keys). - */ - transfer_privs: string[]; - - transfer_pub: EddsaPublicKeyString; - - link_sigs: EddsaSignatureString[]; - - /** - * Iff the corresponding denomination has support for age restriction, - * the client MUST provide the original age commitment, i.e. the vector - * of public keys. - */ - old_age_commitment?: Edx25519PublicKeyEnc[]; -} - -interface DepositConfirmationSignature { - // The EdDSA signature of `TALER_DepositConfirmationPS` using a current - // `signing key of the exchange <sign-key-priv>` affirming the successful - // deposit and that the exchange will transfer the funds after the refund - // deadline, or as soon as possible if the refund deadline is zero. - exchange_sig: EddsaSignatureString; -} - -export interface BatchDepositSuccess { - // Optional base URL of the exchange for looking up wire transfers - // associated with this transaction. If not given, - // the base URL is the same as the one used for this request. - // Can be used if the base URL for ``/transactions/`` differs from that - // for ``/coins/``, i.e. for load balancing. Clients SHOULD - // respect the ``transaction_base_url`` if provided. Any HTTP server - // belonging to an exchange MUST generate a 307 or 308 redirection - // to the correct base URL should a client uses the wrong base - // URL, or if the base URL has changed since the deposit. - transaction_base_url?: string; - - // Timestamp when the deposit was received by the exchange. - exchange_timestamp: TalerProtocolTimestamp; - - // `Public EdDSA key of the exchange <sign-key-pub>` that was used to - // generate the signature. - // Should match one of the exchange's signing keys from ``/keys``. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; - - // Array of deposit confirmation signatures from the exchange - // Entries must be in the same order the coins were given - // in the batch deposit request. - exchange_sig: EddsaSignatureString; -} - -export const codecForBatchDepositSuccess = (): Codec<BatchDepositSuccess> => - buildCodecForObject<BatchDepositSuccess>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("exchange_timestamp", codecForTimestamp) - .property("transaction_base_url", codecOptional(codecForString())) - .build("BatchDepositSuccess"); - -export interface TrackTransactionWired { - // Raw wire transfer identifier of the deposit. - wtid: Base32String; - - // When was the wire transfer given to the bank. - execution_time: TalerProtocolTimestamp; - - // The contribution of this coin to the total (without fees) - coin_contribution: AmountString; - - // Binary-only Signature_ with purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE - // over a TALER_ConfirmWirePS - // whereby the exchange affirms the successful wire transfer. - exchange_sig: EddsaSignatureString; - - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. Again given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; -} - -export const codecForTackTransactionWired = (): Codec<TrackTransactionWired> => - buildCodecForObject<TrackTransactionWired>() - .property("wtid", codecForString()) - .property("execution_time", codecForTimestamp) - .property("coin_contribution", codecForAmountString()) - .property("exchange_sig", codecForString()) - .property("exchange_pub", codecForString()) - .build("TackTransactionWired"); - -interface TrackTransactionAccepted { - // Legitimization target that the merchant should - // use to check for its KYC status using - // the /kyc-check/$REQUIREMENT_ROW/... endpoint. - // Optional, not present if the deposit has not - // yet been aggregated to the point that a KYC - // need has been evaluated. - requirement_row?: number; - - // True if the KYC check for the merchant has been - // satisfied. False does not mean that KYC - // is strictly needed, unless also a - // legitimization_uuid is provided. - kyc_ok: boolean; - - // Time by which the exchange currently thinks the deposit will be executed. - // Actual execution may be later if the KYC check is not satisfied by then. - execution_time: TalerProtocolTimestamp; -} - -export const codecForTackTransactionAccepted = - (): Codec<TrackTransactionAccepted> => - buildCodecForObject<TrackTransactionAccepted>() - .property("requirement_row", codecOptional(codecForNumber())) - .property("kyc_ok", codecForBoolean()) - .property("execution_time", codecForTimestamp) - .build("TackTransactionAccepted"); - -export type TrackTransaction = - | ({ type: "accepted" } & TrackTransactionAccepted) - | ({ type: "wired" } & TrackTransactionWired); - -export interface PurseDeposit { - /** - * Amount to be deposited, can be a fraction of the - * coin's total value. - */ - amount: AmountString; - - /** - * Hash of denomination RSA key with which the coin is signed. - */ - denom_pub_hash: HashCodeString; - - /** - * Exchange's unblinded RSA signature of the coin. - */ - ub_sig: UnblindedSignature; - - /** - * Age commitment for the coin, if the denomination is age-restricted. - */ - age_commitment?: string[]; - - /** - * Attestation for the minimum age, if the denomination is age-restricted. - */ - attest?: string; - - /** - * Signature over TALER_PurseDepositSignaturePS - * of purpose TALER_SIGNATURE_WALLET_PURSE_DEPOSIT - * made by the customer with the - * coin's private key. - */ - coin_sig: EddsaSignatureString; - - /** - * Public key of the coin being deposited into the purse. - */ - coin_pub: EddsaPublicKeyString; -} - -export interface ExchangePurseMergeRequest { - // payto://-URI of the account the purse is to be merged into. - // Must be of the form: 'payto://taler/$EXCHANGE_URL/$RESERVE_PUB'. - payto_uri: string; - - // EdDSA signature of the account/reserve affirming the merge - // over a TALER_AccountMergeSignaturePS. - // Must be of purpose TALER_SIGNATURE_ACCOUNT_MERGE - reserve_sig: EddsaSignatureString; - - // EdDSA signature of the purse private key affirming the merge - // over a TALER_PurseMergeSignaturePS. - // Must be of purpose TALER_SIGNATURE_PURSE_MERGE. - merge_sig: EddsaSignatureString; - - // Client-side timestamp of when the merge request was made. - merge_timestamp: TalerProtocolTimestamp; -} - -export interface ExchangeGetContractResponse { - purse_pub: string; - econtract_sig: string; - econtract: string; -} - -export const codecForExchangeGetContractResponse = - (): Codec<ExchangeGetContractResponse> => - buildCodecForObject<ExchangeGetContractResponse>() - .property("purse_pub", codecForString()) - .property("econtract_sig", codecForString()) - .property("econtract", codecForString()) - .build("ExchangeGetContractResponse"); - -/** - * Contract terms between two wallets (as opposed to a merchant and wallet). - */ -export interface PeerContractTerms { - amount: AmountString; - summary: string; - purse_expiration: TalerProtocolTimestamp; -} - -export interface EncryptedContract { - // Encrypted contract. - econtract: string; - - // Signature over the (encrypted) contract. - econtract_sig: string; - - // Ephemeral public key for the DH operation to decrypt the encrypted contract. - contract_pub: string; -} - -/** - * Payload for /reserves/{reserve_pub}/purse - * endpoint of the exchange. - */ -export interface ExchangeReservePurseRequest { - /** - * Minimum amount that must be credited to the reserve, that is - * the total value of the purse minus the deposit fees. - * If the deposit fees are lower, the contribution to the - * reserve can be higher! - */ - purse_value: AmountString; - - // Minimum age required for all coins deposited into the purse. - min_age: number; - - // Purse fee the reserve owner is willing to pay - // for the purse creation. Optional, if not present - // the purse is to be created from the purse quota - // of the reserve. - purse_fee: AmountString; - - // Optional encrypted contract, in case the buyer is - // proposing the contract and thus establishing the - // purse with the payment. - econtract?: EncryptedContract; - - // EdDSA public key used to approve merges of this purse. - merge_pub: EddsaPublicKeyString; - - // EdDSA signature of the purse private key affirming the merge - // over a TALER_PurseMergeSignaturePS. - // Must be of purpose TALER_SIGNATURE_PURSE_MERGE. - merge_sig: EddsaSignatureString; - - // EdDSA signature of the account/reserve affirming the merge. - // Must be of purpose TALER_SIGNATURE_WALLET_ACCOUNT_MERGE - reserve_sig: EddsaSignatureString; - - // Purse public key. - purse_pub: EddsaPublicKeyString; - - // EdDSA signature of the purse over - // TALER_PurseRequestSignaturePS of - // purpose TALER_SIGNATURE_PURSE_REQUEST - // confirming that the - // above details hold for this purse. - purse_sig: EddsaSignatureString; - - // SHA-512 hash of the contact of the purse. - h_contract_terms: HashCodeString; - - // Client-side timestamp of when the merge request was made. - merge_timestamp: TalerProtocolTimestamp; - - // Indicative time by which the purse should expire - // if it has not been paid. - purse_expiration: TalerProtocolTimestamp; -} - -export interface ExchangePurseDeposits { - // Array of coins to deposit into the purse. - deposits: PurseDeposit[]; -} - -/** - * @deprecated batch deposit should be used. - */ -export interface ExchangeDepositRequest { - // Amount to be deposited, can be a fraction of the - // coin's total value. - contribution: AmountString; - - // The merchant's account details. - // In case of an auction policy, it refers to the seller. - merchant_payto_uri: string; - - // The salt is used to hide the payto_uri from customers - // when computing the h_wire of the merchant. - wire_salt: string; - - // SHA-512 hash of the contract of the merchant with the customer. Further - // details are never disclosed to the exchange. - h_contract_terms: HashCodeString; - - // Hash of denomination RSA key with which the coin is signed. - denom_pub_hash: HashCodeString; - - // Exchange's unblinded RSA signature of the coin. - ub_sig: UnblindedSignature; - - // Timestamp when the contract was finalized. - timestamp: TalerProtocolTimestamp; - - // Indicative time by which the exchange undertakes to transfer the funds to - // the merchant, in case of successful payment. A wire transfer deadline of 'never' - // is not allowed. - wire_transfer_deadline: TalerProtocolTimestamp; - - // EdDSA public key of the merchant, so that the client can identify the - // merchant for refund requests. - // - // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a - // policy via extension. - merchant_pub: EddsaPublicKeyString; - - // Date until which the merchant can issue a refund to the customer via the - // exchange, to be omitted if refunds are not allowed. - // - // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a - // policy via extension. - refund_deadline?: TalerProtocolTimestamp; - - // CAVEAT: THIS IS WORK IN PROGRESS - // (Optional) policy for the deposit. - // This might be a refund, auction or escrow policy. - // - // Note that support for policies is an optional feature of the exchange. - // Optional features are so called "extensions" in Taler. The exchange - // provides the list of supported extensions, including policies, in the - // ExtensionsManifestsResponse response to the /keys endpoint. - policy?: any; - - // Signature over TALER_DepositRequestPS, made by the customer with the - // coin's private key. - coin_sig: EddsaSignatureString; - - h_age_commitment?: string; -} - -export type WireSalt = string; - -export interface ExchangeBatchDepositRequest { - // The merchant's account details. - merchant_payto_uri: string; - - // The salt is used to hide the ``payto_uri`` from customers - // when computing the ``h_wire`` of the merchant. - wire_salt: WireSalt; - - // SHA-512 hash of the contract of the merchant with the customer. Further - // details are never disclosed to the exchange. - h_contract_terms: HashCodeString; - - // The list of coins that are going to be deposited with this Request. - coins: BatchDepositRequestCoin[]; - - // Timestamp when the contract was finalized. - timestamp: TalerProtocolTimestamp; - - // Indicative time by which the exchange undertakes to transfer the funds to - // the merchant, in case of successful payment. A wire transfer deadline of 'never' - // is not allowed. - wire_transfer_deadline: TalerProtocolTimestamp; - - // EdDSA `public key of the merchant <merchant-pub>`, so that the client can identify the - // merchant for refund requests. - merchant_pub: EddsaPublicKeyString; - - // Date until which the merchant can issue a refund to the customer via the - // exchange, to be omitted if refunds are not allowed. - // - // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a - // policy via extension. - refund_deadline?: TalerProtocolTimestamp; - - // CAVEAT: THIS IS WORK IN PROGRESS - // (Optional) policy for the batch-deposit. - // This might be a refund, auction or escrow policy. - policy?: any; -} - -export interface BatchDepositRequestCoin { - // EdDSA public key of the coin being deposited. - coin_pub: EddsaPublicKeyString; - - // Hash of denomination RSA key with which the coin is signed. - denom_pub_hash: HashCodeString; - - // Exchange's unblinded RSA signature of the coin. - ub_sig: UnblindedSignature; - - // Amount to be deposited, can be a fraction of the - // coin's total value. - contribution: Amounts; - - // Signature over `TALER_DepositRequestPS`, made by the customer with the - // `coin's private key <coin-priv>`. - coin_sig: EddsaSignatureString; - - h_age_commitment?: string; -} - -export interface WalletKycUuid { - // UUID that the wallet should use when initiating - // the KYC check. - requirement_row: number; - - // Hash of the payto:// account URI for the wallet. - h_payto: string; -} - -export const codecForWalletKycUuid = (): Codec<WalletKycUuid> => - buildCodecForObject<WalletKycUuid>() - .property("requirement_row", codecForNumber()) - .property("h_payto", codecForString()) - .build("WalletKycUuid"); - -export interface MerchantUsingTemplateDetails { - summary?: string; - amount?: AmountString; -} - -export interface ExchangeRefundRequest { - // 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: AmountString; - - // SHA-512 hash of the contact of the merchant with the customer. - h_contract_terms: HashCodeString; - - // 64-bit transaction id of the refund transaction between merchant and customer. - rtransaction_id: number; - - // EdDSA public key of the merchant. - merchant_pub: EddsaPublicKeyString; - - // EdDSA signature of the merchant over a - // TALER_RefundRequestPS with purpose - // TALER_SIGNATURE_MERCHANT_REFUND - // affirming the refund. - merchant_sig: EddsaPublicKeyString; -} - -export interface ExchangeRefundSuccessResponse { - // The EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND over - // a TALER_RecoupRefreshConfirmationPS - // using a current signing key of the - // exchange affirming the successful refund. - exchange_sig: EddsaSignatureString; - - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; -} - -export const codecForExchangeRefundSuccessResponse = - (): Codec<ExchangeRefundSuccessResponse> => - buildCodecForObject<ExchangeRefundSuccessResponse>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .build("ExchangeRefundSuccessResponse"); - -export type AccountRestriction = - | RegexAccountRestriction - | DenyAllAccountRestriction; - -export interface DenyAllAccountRestriction { - type: "deny"; -} - -// Accounts interacting with this type of account -// restriction must have a payto://-URI matching -// the given regex. -export interface RegexAccountRestriction { - type: "regex"; - - // Regular expression that the payto://-URI of the - // partner account must follow. The regular expression - // should follow posix-egrep, but without support for character - // classes, GNU extensions, back-references or intervals. See - // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html - // for a description of the posix-egrep syntax. Applications - // may support regexes with additional features, but exchanges - // must not use such regexes. - payto_regex: string; - - // Hint for a human to understand the restriction - // (that is hopefully easier to comprehend than the regex itself). - human_hint: string; - - // Map from IETF BCP 47 language tags to localized - // human hints. - human_hint_i18n?: InternationalizedString; -} - -export interface ExchangeWireAccount { - // payto:// URI identifying the account and wire method - payto_uri: string; - - // URI to convert amounts from or to the currency used by - // this wire account of the exchange. Missing if no - // conversion is applicable. - conversion_url?: string; - - // Restrictions that apply to bank accounts that would send - // funds to the exchange (crediting this exchange bank account). - // Optional, empty array for unrestricted. - credit_restrictions: AccountRestriction[]; - - // Restrictions that apply to bank accounts that would receive - // funds from the exchange (debiting this exchange bank account). - // Optional, empty array for unrestricted. - debit_restrictions: AccountRestriction[]; - - // Signature using the exchange's offline key over - // a TALER_MasterWireDetailsPS - // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. - master_sig: EddsaSignatureString; - - // Display label wallets should use to show this - // bank account. - // Since protocol **v19**. - bank_label?: string; - priority?: number; -} - -export const codecForExchangeWireAccount = (): Codec<ExchangeWireAccount> => - buildCodecForObject<ExchangeWireAccount>() - .property("conversion_url", codecOptional(codecForStringURL())) - .property("credit_restrictions", codecForList(codecForAny())) - .property("debit_restrictions", codecForList(codecForAny())) - .property("master_sig", codecForString()) - .property("payto_uri", codecForString()) - .property("bank_label", codecOptional(codecForString())) - .property("priority", codecOptional(codecForNumber())) - .build("WireAccount"); - -export type Integer = number; - -export interface BankConversionInfoConfig { - // libtool-style representation of the Bank protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Name of the API. - name: "taler-conversion-info"; - - regional_currency: string; - - fiat_currency: string; - - // Currency used by this bank. - regional_currency_specification: CurrencySpecification; - - // External currency used during conversion. - fiat_currency_specification: CurrencySpecification; -} - -export const codecForBankConversionInfoConfig = - (): Codec<BankConversionInfoConfig> => - buildCodecForObject<BankConversionInfoConfig>() - .property("name", codecForConstString("taler-conversion-info")) - .property("version", codecForString()) - .property("fiat_currency", codecForString()) - .property("regional_currency", codecForString()) - .property("fiat_currency_specification", codecForCurrencySpecificiation()) - .property( - "regional_currency_specification", - codecForCurrencySpecificiation(), - ) - .build("BankConversionInfoConfig"); - -export interface DenominationExpiredMessage { - // Taler error code. Note that beyond - // expiration this message format is also - // used if the key is not yet valid, or - // has been revoked. - code: number; - - // Signature by the exchange over a - // TALER_DenominationExpiredAffirmationPS. - // Must have purpose TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED. - exchange_sig: EddsaSignatureString; - - // Public key of the exchange used to create - // the 'exchange_sig. - exchange_pub: EddsaPublicKeyString; - - // Hash of the denomination public key that is unknown. - h_denom_pub: HashCodeString; - - // When was the signature created. - timestamp: TalerProtocolTimestamp; - - // What kind of operation was requested that now - // failed? - oper: string; -} - -export const codecForDenominationExpiredMessage = () => - buildCodecForObject<DenominationExpiredMessage>() - .property("code", codecForNumber()) - .property("exchange_sig", codecForString()) - .property("exchange_pub", codecForString()) - .property("h_denom_pub", codecForString()) - .property("timestamp", codecForTimestamp) - .property("oper", codecForString()) - .build("DenominationExpiredMessage"); - -export interface CoinHistoryResponse { - // Current balance of the coin. - balance: AmountString; - - // Hash of the coin's denomination. - h_denom_pub: HashCodeString; - - // Transaction history for the coin. - history: any[]; -} - -export const codecForCoinHistoryResponse = () => - buildCodecForObject<CoinHistoryResponse>() - .property("balance", codecForAmountString()) - .property("h_denom_pub", codecForString()) - .property("history", codecForAny()) - .build("CoinHistoryResponse"); diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts @@ -15,7 +15,7 @@ */ import test from "ava"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; import { parseAddExchangeUri, parseDevExperimentUri, diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts @@ -27,7 +27,7 @@ import { Codec, Context, DecodingError, renderContext } from "./codec.js"; import { canonicalizeBaseUrl } from "./helpers.js"; import { opFixedSuccess, opKnownTalerFailure } from "./operation.js"; import { TalerErrorCode } from "./taler-error-codes.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; import { URL, URLSearchParams } from "./url.js"; /** diff --git a/packages/taler-util/src/transaction-test-data.ts b/packages/taler-util/src/transaction-test-data.ts @@ -18,8 +18,8 @@ import { TransactionType, PaymentStatus, TransactionMajorState, -} from "./transactions-types.js"; -import { RefreshReason } from "./wallet-types.js"; +} from "./types-taler-wallet-transactions.js"; +import { RefreshReason } from "./types-taler-wallet.js"; /** * Sample transaction list entries. diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts @@ -1,803 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type and schema definitions for the wallet's transaction list. - * - * @author Florian Dold - * @author Torsten Grote - */ - -/** - * Imports. - */ -import { - Codec, - buildCodecForObject, - codecForAny, - codecForBoolean, - codecForConstString, - codecForEither, - codecForList, - codecForString, - codecOptional, -} from "./codec.js"; -import { - AmountString, - InternationalizedString, - MerchantInfo, - codecForInternationalizedString, - codecForMerchantInfo, -} from "./taler-types.js"; -import { TalerPreciseTimestamp, TalerProtocolTimestamp } from "./time.js"; -import { - RefreshReason, - ScopeInfo, - TalerErrorDetail, - TransactionIdStr, - TransactionStateFilter, - WithdrawalExchangeAccountDetails, - codecForScopeInfo, -} from "./wallet-types.js"; - -export interface TransactionsRequest { - /** - * return only transactions in the given currency - * - * it will be removed in next release - * - * @deprecated use scopeInfo - */ - currency?: string; - - /** - * return only transactions in the given scopeInfo - */ - scopeInfo?: ScopeInfo; - - /** - * if present, results will be limited to transactions related to the given search string - */ - search?: string; - - /** - * Sort order of the transaction items. - * By default, items are sorted ascending by their - * main timestamp. - * - * ascending: ascending by timestamp, but pending transactions first - * descending: ascending by timestamp, but pending transactions first - * stable-ascending: ascending by timestamp, with pending transactions amidst other transactions - * (stable in the sense of: pending transactions don't jump around) - */ - sort?: "ascending" | "descending" | "stable-ascending"; - - /** - * If true, include all refreshes in the transactions list. - */ - includeRefreshes?: boolean; - - filterByState?: TransactionStateFilter; -} - -export interface TransactionState { - major: TransactionMajorState; - minor?: TransactionMinorState; -} - -export enum TransactionMajorState { - // No state, only used when reporting transitions into the initial state - None = "none", - Pending = "pending", - Done = "done", - Aborting = "aborting", - Aborted = "aborted", - Dialog = "dialog", - Finalizing = "finalizing", - // Plain suspended is always a suspended pending state. - Suspended = "suspended", - SuspendedFinalizing = "suspended-finalizing", - SuspendedAborting = "suspended-aborting", - Failed = "failed", - Expired = "expired", - // Only used for the notification, never in the transaction history - Deleted = "deleted", -} - -export enum TransactionMinorState { - // Placeholder until D37 is fully implemented - Unknown = "unknown", - Deposit = "deposit", - KycRequired = "kyc", - AmlRequired = "aml", - MergeKycRequired = "merge-kyc", - Track = "track", - SubmitPayment = "submit-payment", - RebindSession = "rebind-session", - Refresh = "refresh", - Pickup = "pickup", - AutoRefund = "auto-refund", - User = "user", - Bank = "bank", - Exchange = "exchange", - ClaimProposal = "claim-proposal", - CheckRefund = "check-refund", - CreatePurse = "create-purse", - DeletePurse = "delete-purse", - RefreshExpired = "refresh-expired", - Ready = "ready", - Merge = "merge", - Repurchase = "repurchase", - BankRegisterReserve = "bank-register-reserve", - BankConfirmTransfer = "bank-confirm-transfer", - WithdrawCoins = "withdraw-coins", - ExchangeWaitReserve = "exchange-wait-reserve", - AbortingBank = "aborting-bank", - Aborting = "aborting", - Refused = "refused", - Withdraw = "withdraw", - MerchantOrderProposed = "merchant-order-proposed", - Proposed = "proposed", - RefundAvailable = "refund-available", - AcceptRefund = "accept-refund", - PaidByOther = "paid-by-other", - CompletedByOtherWallet = "completed-by-other-wallet", -} - -export enum TransactionAction { - Delete = "delete", - Suspend = "suspend", - Resume = "resume", - Abort = "abort", - Fail = "fail", - Retry = "retry", -} - -export interface TransactionsResponse { - // a list of past and pending transactions sorted by pending, timestamp and transactionId. - // In case two events are both pending and have the same timestamp, - // they are sorted by the transactionId - // (lexically ascending and locale-independent comparison). - transactions: Transaction[]; -} - -export interface TransactionCommon { - // opaque unique ID for the transaction, used as a starting point for paginating queries - // and for invoking actions on the transaction (e.g. deleting/hiding it from the history) - transactionId: TransactionIdStr; - - // the type of the transaction; different types might provide additional information - type: TransactionType; - - // main timestamp of the transaction - timestamp: TalerPreciseTimestamp; - - /** - * Transaction state, as per DD37. - */ - txState: TransactionState; - - /** - * Possible transitions based on the current state. - */ - txActions: TransactionAction[]; - - /** - * Raw amount of the transaction (exclusive of fees or other extra costs). - */ - amountRaw: AmountString; - - /** - * Amount added or removed from the wallet's balance (including all fees and other costs). - */ - amountEffective: AmountString; - - error?: TalerErrorDetail; - - /** - * If the transaction minor state is in KycRequired this field is going to - * have the location where the user need to go to complete KYC information. - */ - kycUrl?: string; -} - -export type Transaction = - | TransactionWithdrawal - | TransactionPayment - | TransactionRefund - | TransactionRefresh - | TransactionDeposit - | TransactionPeerPullCredit - | TransactionPeerPullDebit - | TransactionPeerPushCredit - | TransactionPeerPushDebit - | TransactionInternalWithdrawal - | TransactionRecoup - | TransactionDenomLoss; - -export enum TransactionType { - Withdrawal = "withdrawal", - InternalWithdrawal = "internal-withdrawal", - Payment = "payment", - Refund = "refund", - Refresh = "refresh", - Deposit = "deposit", - PeerPushDebit = "peer-push-debit", - PeerPushCredit = "peer-push-credit", - PeerPullDebit = "peer-pull-debit", - PeerPullCredit = "peer-pull-credit", - Recoup = "recoup", - DenomLoss = "denom-loss", -} - -export enum WithdrawalType { - TalerBankIntegrationApi = "taler-bank-integration-api", - ManualTransfer = "manual-transfer", -} - -export type WithdrawalDetails = - | WithdrawalDetailsForManualTransfer - | WithdrawalDetailsForTalerBankIntegrationApi; - -interface WithdrawalDetailsForManualTransfer { - type: WithdrawalType.ManualTransfer; - - /** - * Payto URIs that the exchange supports. - * - * Already contains the amount and message. - * - * @deprecated in favor of exchangeCreditAccounts - */ - exchangePaytoUris: string[]; - - exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[]; - - // Public key of the reserve - reservePub: string; - - /** - * Is the reserve ready for withdrawal? - */ - reserveIsReady: boolean; -} - -interface WithdrawalDetailsForTalerBankIntegrationApi { - type: WithdrawalType.TalerBankIntegrationApi; - - /** - * Set to true if the bank has confirmed the withdrawal, false if not. - * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI. - * See also bankConfirmationUrl below. - */ - confirmed: boolean; - - /** - * If the withdrawal is unconfirmed, this can include a URL for user - * initiated confirmation. - */ - bankConfirmationUrl?: string; - - // Public key of the reserve - reservePub: string; - - /** - * Is the reserve ready for withdrawal? - */ - reserveIsReady: boolean; - - /** - * Is the bank transfer for the withdrawal externally confirmed? - */ - externalConfirmation?: boolean; - - exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[]; -} - -export enum DenomLossEventType { - DenomExpired = "denom-expired", - DenomVanished = "denom-vanished", - DenomUnoffered = "denom-unoffered", -} - -/** - * A transaction to indicate financial loss due to denominations - * that became unusable for deposits. - */ -export interface TransactionDenomLoss extends TransactionCommon { - type: TransactionType.DenomLoss; - lossEventType: DenomLossEventType; - exchangeBaseUrl: string; -} - -/** - * A withdrawal transaction (either bank-integrated or manual). - */ -export interface TransactionWithdrawal extends TransactionCommon { - type: TransactionType.Withdrawal; - - /** - * Exchange of the withdrawal. - */ - exchangeBaseUrl: string | undefined; - - /** - * Amount that got subtracted from the reserve balance. - */ - amountRaw: AmountString; - - /** - * Amount that actually was (or will be) added to the wallet's balance. - */ - amountEffective: AmountString; - - withdrawalDetails: WithdrawalDetails; -} - -/** - * Internal withdrawal operation, only reported on request. - * - * Some transactions (peer-*-credit) internally do a withdrawal, - * but only the peer-*-credit transaction is reported. - * - * The internal withdrawal transaction allows to access the details of - * the underlying withdrawal for testing/debugging. - * - * It is usually not reported, so that amounts of transactions properly - * add up, since the amountEffecive of the withdrawal is already reported - * in the peer-*-credit transaction. - */ -export interface TransactionInternalWithdrawal extends TransactionCommon { - type: TransactionType.InternalWithdrawal; - - /** - * Exchange of the withdrawal. - */ - exchangeBaseUrl: string; - - /** - * Amount that got subtracted from the reserve balance. - */ - amountRaw: AmountString; - - /** - * Amount that actually was (or will be) added to the wallet's balance. - */ - amountEffective: AmountString; - - withdrawalDetails: WithdrawalDetails; -} - -export interface PeerInfoShort { - expiration: TalerProtocolTimestamp | undefined; - summary: string | undefined; -} - -/** - * Credit because we were paid for a P2P invoice we created. - */ -export interface TransactionPeerPullCredit extends TransactionCommon { - type: TransactionType.PeerPullCredit; - - info: PeerInfoShort; - /** - * Exchange used. - */ - exchangeBaseUrl: string; - - /** - * Amount that got subtracted from the reserve balance. - */ - amountRaw: AmountString; - - /** - * Amount that actually was (or will be) added to the wallet's balance. - */ - amountEffective: AmountString; - - /** - * URI to send to the other party. - * - * Only available in the right state. - */ - talerUri: string | undefined; -} - -/** - * Debit because we paid someone's invoice. - */ -export interface TransactionPeerPullDebit extends TransactionCommon { - type: TransactionType.PeerPullDebit; - - info: PeerInfoShort; - /** - * Exchange used. - */ - exchangeBaseUrl: string; - - amountRaw: AmountString; - - amountEffective: AmountString; -} - -/** - * We sent money via a P2P payment. - */ -export interface TransactionPeerPushDebit extends TransactionCommon { - type: TransactionType.PeerPushDebit; - - info: PeerInfoShort; - /** - * Exchange used. - */ - exchangeBaseUrl: string; - - /** - * Amount that got subtracted from the reserve balance. - */ - amountRaw: AmountString; - - /** - * Amount that actually was (or will be) added to the wallet's balance. - */ - amountEffective: AmountString; - - /** - * URI to accept the payment. - * - * Only present if the transaction is in a state where the other party can - * accept the payment. - */ - talerUri?: string; -} - -/** - * We received money via a P2P payment. - */ -export interface TransactionPeerPushCredit extends TransactionCommon { - type: TransactionType.PeerPushCredit; - - info: PeerInfoShort; - /** - * Exchange used. - */ - exchangeBaseUrl: string; - - /** - * Amount that got subtracted from the reserve balance. - */ - amountRaw: AmountString; - - /** - * Amount that actually was (or will be) added to the wallet's balance. - */ - amountEffective: AmountString; -} - -/** - * The exchange revoked a key and the wallet recoups funds. - */ -export interface TransactionRecoup extends TransactionCommon { - type: TransactionType.Recoup; -} - -export enum PaymentStatus { - /** - * Explicitly aborted after timeout / failure - */ - Aborted = "aborted", - - /** - * Payment failed, wallet will auto-retry. - * User should be given the option to retry now / abort. - */ - Failed = "failed", - - /** - * Paid successfully - */ - Paid = "paid", - - /** - * User accepted, payment is processing. - */ - Accepted = "accepted", -} - -export interface TransactionPayment extends TransactionCommon { - type: TransactionType.Payment; - - /** - * Additional information about the payment. - */ - info: OrderShortInfo; - - /** - * Wallet-internal end-to-end identifier for the payment. - */ - proposalId: string; - - /** - * Amount that must be paid for the contract - */ - amountRaw: AmountString; - - /** - * Amount that was paid, including deposit, wire and refresh fees. - */ - amountEffective: AmountString; - - /** - * Amount that has been refunded by the merchant - */ - totalRefundRaw: AmountString; - - /** - * Amount will be added to the wallet's balance after fees and refreshing - */ - totalRefundEffective: AmountString; - - /** - * Amount pending to be picked up - */ - refundPending: AmountString | undefined; - - /** - * Reference to applied refunds - */ - refunds: RefundInfoShort[]; - - /** - * Is the wallet currently checking for a refund? - */ - refundQueryActive: boolean; - - /** - * Does this purchase has an pos validation - */ - posConfirmation: string | undefined; -} - -export interface OrderShortInfo { - /** - * Order ID, uniquely identifies the order within a merchant instance - */ - orderId: string; - - /** - * Hash of the contract terms. - */ - contractTermsHash: string; - - /** - * More information about the merchant - */ - merchant: MerchantInfo; - - /** - * Summary of the order, given by the merchant - */ - summary: string; - - /** - * Map from IETF BCP 47 language tags to localized summaries - */ - summary_i18n?: InternationalizedString; - - /** - * URL of the fulfillment, given by the merchant - */ - fulfillmentUrl?: string; - - /** - * Plain text message that should be shown to the user - * when the payment is complete. - */ - fulfillmentMessage?: string; - - /** - * Translations of fulfillmentMessage. - */ - fulfillmentMessage_i18n?: InternationalizedString; -} - -export interface RefundInfoShort { - transactionId: string; - timestamp: TalerProtocolTimestamp; - amountEffective: AmountString; - amountRaw: AmountString; -} - -/** - * Summary information about the payment that we got a refund for. - */ -export interface RefundPaymentInfo { - summary: string; - summary_i18n?: InternationalizedString; - /** - * More information about the merchant - */ - merchant: MerchantInfo; -} - -export interface TransactionRefund extends TransactionCommon { - type: TransactionType.Refund; - - // Amount that has been refunded by the merchant - amountRaw: AmountString; - - // Amount will be added to the wallet's balance after fees and refreshing - amountEffective: AmountString; - - // ID for the transaction that is refunded - refundedTransactionId: string; - - paymentInfo: RefundPaymentInfo | undefined; -} - -/** - * A transaction shown for refreshes. - * Only shown for (1) refreshes not associated with other transactions - * and (2) refreshes in an error state. - */ -export interface TransactionRefresh extends TransactionCommon { - type: TransactionType.Refresh; - - refreshReason: RefreshReason; - - /** - * Transaction ID that caused this refresh. - */ - originatingTransactionId?: string; - - /** - * Always zero for refreshes - */ - amountRaw: AmountString; - - /** - * Fees, i.e. the effective, negative effect of the refresh - * on the balance. - * - * Only applicable for stand-alone refreshes, and zero for - * other refreshes where the transaction itself accounts for the - * refresh fee. - */ - amountEffective: AmountString; - - refreshInputAmount: AmountString; - refreshOutputAmount: AmountString; -} - -export interface DepositTransactionTrackingState { - // Raw wire transfer identifier of the deposit. - wireTransferId: string; - // When was the wire transfer given to the bank. - timestampExecuted: TalerProtocolTimestamp; - // Total amount transfer for this wtid (including fees) - amountRaw: AmountString; - // Wire fee amount for this exchange - wireFee: AmountString; -} - -/** - * Deposit transaction, which effectively sends - * money from this wallet somewhere else. - */ -export interface TransactionDeposit extends TransactionCommon { - type: TransactionType.Deposit; - - depositGroupId: string; - - /** - * Target for the deposit. - */ - targetPaytoUri: string; - - /** - * Raw amount that is being deposited - */ - amountRaw: AmountString; - - /** - * Effective amount that is being deposited - */ - amountEffective: AmountString; - - wireTransferDeadline: TalerProtocolTimestamp; - - wireTransferProgress: number; - - /** - * Did all the deposit requests succeed? - */ - deposited: boolean; - - trackingState: Array<DepositTransactionTrackingState>; -} - -export interface TransactionByIdRequest { - transactionId: string; -} - -export const codecForTransactionByIdRequest = - (): Codec<TransactionByIdRequest> => - buildCodecForObject<TransactionByIdRequest>() - .property("transactionId", codecForString()) - .build("TransactionByIdRequest"); - -export interface WithdrawalTransactionByURIRequest { - talerWithdrawUri: string; -} - -export const codecForWithdrawalTransactionByURIRequest = - (): Codec<WithdrawalTransactionByURIRequest> => - buildCodecForObject<WithdrawalTransactionByURIRequest>() - .property("talerWithdrawUri", codecForString()) - .build("WithdrawalTransactionByURIRequest"); - -export const codecForTransactionsRequest = (): Codec<TransactionsRequest> => - buildCodecForObject<TransactionsRequest>() - .property("currency", codecOptional(codecForString())) - .property("scopeInfo", codecOptional(codecForScopeInfo())) - .property("search", codecOptional(codecForString())) - .property( - "sort", - codecOptional( - codecForEither( - codecForConstString("ascending"), - codecForConstString("descending"), - codecForConstString("stable-ascending"), - ), - ), - ) - .property("includeRefreshes", codecOptional(codecForBoolean())) - .build("TransactionsRequest"); - -// FIXME: do full validation here! -export const codecForTransactionsResponse = (): Codec<TransactionsResponse> => - buildCodecForObject<TransactionsResponse>() - .property("transactions", codecForList(codecForAny())) - .build("TransactionsResponse"); - -export const codecForOrderShortInfo = (): Codec<OrderShortInfo> => - buildCodecForObject<OrderShortInfo>() - .property("contractTermsHash", codecForString()) - .property("fulfillmentMessage", codecOptional(codecForString())) - .property( - "fulfillmentMessage_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("fulfillmentUrl", codecOptional(codecForString())) - .property("merchant", codecForMerchantInfo()) - .property("orderId", codecForString()) - .property("summary", codecForString()) - .property("summary_i18n", codecOptional(codecForInternationalizedString())) - .build("OrderShortInfo"); - -export interface ListAssociatedRefreshesRequest { - transactionId: string; -} - -export const codecForListAssociatedRefreshesRequest = - (): Codec<ListAssociatedRefreshesRequest> => - buildCodecForObject<ListAssociatedRefreshesRequest>() - .property("transactionId", codecForString()) - .build("ListAssociatedRefreshesRequest"); - -export interface ListAssociatedRefreshesResponse { - transactionIds: string[]; -} diff --git a/packages/taler-util/src/twrpc.ts b/packages/taler-util/src/twrpc.ts @@ -14,8 +14,6 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { CoreApiResponse } from "./wallet-types.js"; - /** * Implementation for the wallet-core IPC protocol. * diff --git a/packages/taler-util/src/types-taler-bank-conversion.ts b/packages/taler-util/src/types-taler-bank-conversion.ts @@ -0,0 +1,200 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { codecForAmountString } from "./amounts.js"; +import { + Codec, + buildCodecForObject, + codecForConstString, + codecForEither, + codecForString, +} from "./codec.js"; +import { + AmountString, + CurrencySpecification, + DecimalNumber, + codecForCurrencySpecificiation, + codecForDecimalNumber, +} from "./types-taler-common.js"; + +export interface ConversionInfo { + // Exchange rate to buy regional currency from fiat + cashin_ratio: DecimalNumber; + + // Exchange rate to sell regional currency for fiat + cashout_ratio: DecimalNumber; + + // Fee to subtract after applying the cashin ratio. + cashin_fee: AmountString; + + // Fee to subtract after applying the cashout ratio. + cashout_fee: AmountString; + + // Minimum amount authorised for cashin, in fiat before conversion + cashin_min_amount: AmountString; + + // Minimum amount authorised for cashout, in regional before conversion + cashout_min_amount: AmountString; + + // Smallest possible regional amount, converted amount is rounded to this amount + cashin_tiny_amount: AmountString; + + // Smallest possible fiat amount, converted amount is rounded to this amount + cashout_tiny_amount: AmountString; + + // Rounding mode used during cashin conversion + cashin_rounding_mode: "zero" | "up" | "nearest"; + + // Rounding mode used during cashout conversion + cashout_rounding_mode: "zero" | "up" | "nearest"; +} + +export interface TalerConversionInfoConfig { + // libtool-style representation of the Bank protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the API. + name: "taler-conversion-info"; + + // Currency used by this bank. + regional_currency: string; + + // How the bank SPA should render this currency. + regional_currency_specification: CurrencySpecification; + + // External currency used during conversion. + fiat_currency: string; + + // How the bank SPA should render this currency. + fiat_currency_specification: CurrencySpecification; + + // Extra conversion rate information. + // Only present if server opts in to report the static conversion rate. + conversion_rate: ConversionInfo; +} + +export interface CashinConversionResponse { + // Amount that the user will get deducted from their fiat + // bank account, according to the 'amount_credit' value. + amount_debit: AmountString; + // Amount that the user will receive in their regional + // bank account, according to 'amount_debit'. + amount_credit: AmountString; +} + +export interface CashoutConversionResponse { + // Amount that the user will get deducted from their regional + // bank account, according to the 'amount_credit' value. + amount_debit: AmountString; + // Amount that the user will receive in their fiat + // bank account, according to 'amount_debit'. + amount_credit: AmountString; +} + +export type RoundingMode = "zero" | "up" | "nearest"; + +export interface ConversionRate { + // Exchange rate to buy regional currency from fiat + cashin_ratio: DecimalNumber; + + // Fee to subtract after applying the cashin ratio. + cashin_fee: AmountString; + + // Minimum amount authorised for cashin, in fiat before conversion + cashin_min_amount: AmountString; + + // Smallest possible regional amount, converted amount is rounded to this amount + cashin_tiny_amount: AmountString; + + // Rounding mode used during cashin conversion + cashin_rounding_mode: RoundingMode; + + // Exchange rate to sell regional currency for fiat + cashout_ratio: DecimalNumber; + + // Fee to subtract after applying the cashout ratio. + cashout_fee: AmountString; + + // Minimum amount authorised for cashout, in regional before conversion + cashout_min_amount: AmountString; + + // Smallest possible fiat amount, converted amount is rounded to this amount + cashout_tiny_amount: AmountString; + + // Rounding mode used during cashout conversion + cashout_rounding_mode: RoundingMode; +} + +export const codecForCashoutConversionResponse = + (): Codec<CashoutConversionResponse> => + buildCodecForObject<CashoutConversionResponse>() + .property("amount_credit", codecForAmountString()) + .property("amount_debit", codecForAmountString()) + .build("TalerCorebankApi.CashoutConversionResponse"); + +export const codecForCashinConversionResponse = + (): Codec<CashinConversionResponse> => + buildCodecForObject<CashinConversionResponse>() + .property("amount_credit", codecForAmountString()) + .property("amount_debit", codecForAmountString()) + .build("TalerCorebankApi.CashinConversionResponse"); + +export const codecForConversionInfo = (): Codec<ConversionInfo> => + buildCodecForObject<ConversionInfo>() + .property("cashin_fee", codecForAmountString()) + .property("cashin_min_amount", codecForAmountString()) + .property("cashin_ratio", codecForDecimalNumber()) + .property( + "cashin_rounding_mode", + codecForEither( + codecForConstString("zero"), + codecForConstString("up"), + codecForConstString("nearest"), + ), + ) + .property("cashin_tiny_amount", codecForAmountString()) + .property("cashout_fee", codecForAmountString()) + .property("cashout_min_amount", codecForAmountString()) + .property("cashout_ratio", codecForDecimalNumber()) + .property( + "cashout_rounding_mode", + codecForEither( + codecForConstString("zero"), + codecForConstString("up"), + codecForConstString("nearest"), + ), + ) + .property("cashout_tiny_amount", codecForAmountString()) + .build("ConversionBankConfig.ConversionInfo"); + +export const codecForConversionBankConfig = (): Codec<TalerConversionInfoConfig> => + buildCodecForObject<TalerConversionInfoConfig>() + .property("name", codecForConstString("taler-conversion-info")) + .property("version", codecForString()) + .property("regional_currency", codecForString()) + .property( + "regional_currency_specification", + codecForCurrencySpecificiation(), + ) + .property("fiat_currency", codecForString()) + .property("fiat_currency_specification", codecForCurrencySpecificiation()) + + .property("conversion_rate", codecForConversionInfo()) + .build("ConversionBankConfig.IntegrationConfig"); diff --git a/packages/taler-util/src/types-taler-bank-integration.ts b/packages/taler-util/src/types-taler-bank-integration.ts @@ -0,0 +1,193 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { Codec, buildCodecForObject, codecForConstString, codecForEither, codecOptional } from "./codec.js"; +import { codecForAmountString, codecForList, codecForString } from "./index.js"; +import { PaytoString, codecForPaytoString } from "./payto.js"; +import { AmountString, CurrencySpecification, codecForCurrencyName, codecForCurrencySpecificiation, codecForLibtoolVersion, codecForURLString } from "./types-taler-common.js"; + +export type WithdrawalOperationStatus = + | "pending" + | "selected" + | "aborted" + | "confirmed"; + +export interface BankVersion { + // libtool-style representation of the Bank protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Currency used by this bank. + currency: string; + + // How the bank SPA should render this currency. + currency_specification?: CurrencySpecification; + + // Name of the API. + name: "taler-bank-integration"; +} + +export interface BankWithdrawalOperationStatus { + // Current status of the operation + // pending: the operation is pending parameters selection (exchange and reserve public key) + // selected: the operations has been selected and is pending confirmation + // aborted: the operation has been aborted + // confirmed: the transfer has been confirmed and registered by the bank + status: WithdrawalOperationStatus; + + // Currency used for the withdrawal. + // MUST be present when amount is absent. + // @since v2, may become mandatory in the future. + currency?: string; + + // Amount that will be withdrawn with this operation + // (raw amount without fee considerations). Only + // given once the amount is fixed and cannot be changed. + // Optional since **vC2EC**. + amount?: AmountString | undefined; + + // Suggestion for the amount to be withdrawn with this + // operation. Given if a suggestion was made but the + // user may still change the amount. + // Optional since **vC2EC**. + suggested_amount?: AmountString | undefined; + + // Maximum amount that the wallet can choose to withdraw. + // Only applicable when the amount is not fixed. + // @since **vC2EC**. + max_amount?: AmountString | undefined; + + // The non-Taler card fees the customer will have + // to pay to the bank / payment service provider + // they are using to make the withdrawal. + // @since **vC2EC** + card_fees?: AmountString | undefined; + + // Bank account of the customer that is debiting, as an + // RFC 8905 payto URI. + sender_wire?: PaytoString; + + // Base URL of the suggested exchange. The bank may have + // neither a suggestion nor a requirement for the exchange. + // This value is typically set in the bank's configuration. + suggested_exchange?: string; + + // Base URL of an exchange that must be used. Optional, + // not given *unless* a particular exchange is mandatory. + // This value is typically set in the bank's configuration. + // @since **vC2EC** + required_exchange?: string; + + // URL that the user needs to navigate to in order to + // complete some final confirmation (e.g. 2FA). + // Only applicable when status is selected or pending. + // It may contain the withdrawal operation id. + confirm_transfer_url?: string; + + // Wire transfer types supported by the bank. + wire_types: string[]; + + // Reserve public key selected by the exchange, + // only non-null if status is selected or confirmed. + selected_reserve_pub?: string; + + // Exchange account selected by the wallet; + // only non-null if status is selected or confirmed. + // @since **v1** + selected_exchange_account?: string; +} + +export interface BankWithdrawalOperationPostRequest { + // Reserve public key that should become the wire transfer + // subject to fund the withdrawal. + reserve_pub: string; + + // Payto address of the exchange selected for the withdrawal. + selected_exchange: PaytoString; + + // Selected amount to be transferred. Optional if the + // backend already knows the amount. + // @since **vC2EC** + amount?: AmountString | undefined; +} + +export interface BankWithdrawalOperationPostResponse { + // Current status of the operation + // pending: the operation is pending parameters selection (exchange and reserve public key) + // selected: the operations has been selected and is pending confirmation + // aborted: the operation has been aborted + // confirmed: the transfer has been confirmed and registered by the bank + status: Omit<"pending", WithdrawalOperationStatus>; + + // URL that the user needs to navigate to in order to + // complete some final confirmation (e.g. 2FA). + // + // Only applicable when status is selected or pending. + // It may contain withdrawal operation id + confirm_transfer_url?: string; +} + + +export const codecForBankVersion = + (): Codec<BankVersion> => + buildCodecForObject<BankVersion>() + .property("currency", codecForCurrencyName()) + .property("currency_specification", codecForCurrencySpecificiation()) + .property("name", codecForConstString("taler-bank-integration")) + .property("version", codecForLibtoolVersion()) + .build("TalerBankIntegrationApi.BankVersion"); + +export const codecForBankWithdrawalOperationStatus = + (): Codec<BankWithdrawalOperationStatus> => + buildCodecForObject<BankWithdrawalOperationStatus>() + .property( + "status", + codecForEither( + codecForConstString("pending"), + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) + .property("amount", codecOptional(codecForAmountString())) + .property("currency", codecOptional(codecForCurrencyName())) + .property("suggested_amount", codecOptional(codecForAmountString())) + .property("card_fees", codecOptional(codecForAmountString())) + .property("sender_wire", codecOptional(codecForPaytoString())) + .property("suggested_exchange", codecOptional(codecForURLString())) + .property("confirm_transfer_url", codecOptional(codecForURLString())) + .property("wire_types", codecForList(codecForString())) + .property("selected_reserve_pub", codecOptional(codecForString())) + .property("selected_exchange_account", codecOptional(codecForString())) + .property("max_amount", codecOptional(codecForAmountString())) + .build("TalerBankIntegrationApi.BankWithdrawalOperationStatus"); + +export const codecForBankWithdrawalOperationPostResponse = + (): Codec<BankWithdrawalOperationPostResponse> => + buildCodecForObject<BankWithdrawalOperationPostResponse>() + .property( + "status", + codecForEither( + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) + .property("confirm_transfer_url", codecOptional(codecForURLString())) + .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse"); +\ No newline at end of file diff --git a/packages/taler-util/src/types-taler-challenger.ts b/packages/taler-util/src/types-taler-challenger.ts @@ -0,0 +1,293 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + Codec, + buildCodecForObject, + buildCodecForUnion, + codecForAny, + codecForBoolean, + codecForConstString, + codecForEither, + codecForMap, + codecForNumber, + codecForString, + codecOptional, +} from "./codec.js"; +import { TalerProtocolTimestamp, codecForTimestamp } from "./time.js"; +import { + Integer, + InternationalizedString, + Timestamp, +} from "./types-taler-common.js"; + +export interface ChallengerTermsOfServiceResponse { + // Name of the service + name: "challenger"; + + // libtool-style representation of the Challenger protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since v0, may become mandatory in the future. + implementation?: string; + + // Object; map of keys (names of the fields of the address + // to be entered by the user) to objects with a "regex" (string) + // containing an extended Posix regular expression for allowed + // address field values, and a "hint"/"hint_i18n" giving a + // human-readable explanation to display if the value entered + // by the user does not match the regex. Keys that are not mapped + // to such an object have no restriction on the value provided by + // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration. + restrictions: Record<string, Restriction> | undefined; + + // @since v2. + address_type: "email" | "phone"; +} + +export interface ChallengeSetupResponse { + // Nonce to use when constructing /authorize endpoint. + nonce: string; +} + +export interface Restriction { + regex?: string; + hint?: string; + hint_i18n?: InternationalizedString; +} + +export interface ChallengeStatus { + // indicates if the given address cannot be changed anymore, the + // form should be read-only if set to true. + fix_address: boolean; + + // form values from the previous submission if available, details depend + // on the ADDRESS_TYPE, should be used to pre-populate the form + last_address: Record<string, string> | undefined; + + // number of times the address can still be changed, may or may not be + // shown to the user + changes_left: Integer; + + // is the challenge already solved? + solved: boolean; + + // when we would re-transmit the challenge the next + // time (at the earliest) if requested by the user + // only present if challenge already created + // @since v2 + retransmission_time: Timestamp; + + // how many times might the PIN still be retransmitted + // only present if challenge already created + // @since v2 + pin_transmissions_left: Integer; + + // how many times might the user still try entering the PIN code + // only present if challenge already created + // @since v2 + auth_attempts_left: Integer; +} + +export type ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse; + +export interface ChallengeRedirect { + type: "completed"; + // challenge is completed, use should redirect here + redirect_url: string; +} + +export interface ChallengeCreateResponse { + type: "created"; + // how many more attempts are allowed, might be shown to the user, + // highlighting might be appropriate for low values such as 1 or 2 (the + // form will never be used if the value is zero) + attempts_left: Integer; + + // the address that is being validated, might be shown or not + address: Object; + + // true if we just retransmitted the challenge, false if we sent a + // challenge recently and thus refused to transmit it again this time; + // might make a useful hint to the user + transmitted: boolean; + + // timestamp explaining when we would re-transmit the challenge the next + // time (at the earliest) if requested by the user + retransmission_time: TalerProtocolTimestamp; +} + +export type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse; + +export interface InvalidPinResponse { + type: "pending"; + + // numeric Taler error code, should be shown to indicate the error + // compactly for reporting to developers + ec?: number; + + // human-readable Taler error code, should be shown for the user to + // understand the error + hint: string; + + // how many times is the user still allowed to change the address; + // if 0, the user should not be shown a link to jump to the + // address entry form + addresses_left: Integer; + + // how many times might the PIN still be retransmitted + pin_transmissions_left: Integer; + + // how many times might the user still try entering the PIN code + auth_attempts_left: Integer; + + // if true, the PIN was not even evaluated as the user previously + // exhausted the number of attempts + exhausted: boolean; + + // if true, the PIN was not even evaluated as no challenge was ever + // issued (the user must have skipped the step of providing their + // address first!) + no_challenge: boolean; +} + +export interface ChallengerAuthResponse { + // Token used to authenticate access in /info. + access_token: string; + + // Type of the access token. + token_type: "Bearer"; + + // Amount of time that an access token is valid (in seconds). + expires_in: Integer; +} + +export interface ChallengerInfoResponse { + // Unique ID of the record within Challenger + // (identifies the rowid of the token). + id: Integer; + + // Address that was validated. + // Key-value pairs, details depend on the + // address_type. + address: Object; + + // Type of the address. + address_type: string; + + // How long do we consider the address to be + // valid for this user. + expires: Timestamp; +} + +export const codecForChallengerTermsOfServiceResponse = + (): Codec<ChallengerTermsOfServiceResponse> => + buildCodecForObject<ChallengerTermsOfServiceResponse>() + .property("name", codecForConstString("challenger")) + .property("version", codecForString()) + .property("implementation", codecOptional(codecForString())) + .property("restrictions", codecOptional(codecForMap(codecForAny()))) + .property( + "address_type", + codecForEither( + codecForConstString("phone"), + codecForConstString("email"), + ), + ) + .build("ChallengerApi.ChallengerTermsOfServiceResponse"); + +export const codecForChallengeSetupResponse = + (): Codec<ChallengeSetupResponse> => + buildCodecForObject<ChallengeSetupResponse>() + .property("nonce", codecForString()) + .build("ChallengerApi.ChallengeSetupResponse"); + +export const codecForChallengeStatus = (): Codec<ChallengeStatus> => + buildCodecForObject<ChallengeStatus>() + .property("fix_address", codecForBoolean()) + .property("solved", codecForBoolean()) + .property("last_address", codecOptional(codecForMap(codecForAny()))) + .property("changes_left", codecForNumber()) + .property("retransmission_time", codecForTimestamp) + .property("pin_transmissions_left", codecForNumber()) + .property("auth_attempts_left", codecForNumber()) + .build("ChallengerApi.ChallengeStatus"); + +export const codecForChallengeResponse = (): Codec<ChallengeResponse> => + buildCodecForUnion<ChallengeResponse>() + .discriminateOn("type") + .alternative("completed", codecForChallengeRedirect()) + .alternative("created", codecForChallengeCreateResponse()) + .build("ChallengerApi.ChallengeResponse"); + +export const codecForChallengeCreateResponse = + (): Codec<ChallengeCreateResponse> => + buildCodecForObject<ChallengeCreateResponse>() + .property("attempts_left", codecForNumber()) + .property("type", codecForConstString("created")) + .property("address", codecForAny()) + .property("transmitted", codecForBoolean()) + .property("retransmission_time", codecForTimestamp) + .build("ChallengerApi.ChallengeCreateResponse"); + +export const codecForChallengeRedirect = (): Codec<ChallengeRedirect> => + buildCodecForObject<ChallengeRedirect>() + .property("type", codecForConstString("completed")) + .property("redirect_url", codecForString()) + .build("ChallengerApi.ChallengeRedirect"); + +export const codecForChallengeInvalidPinResponse = + (): Codec<InvalidPinResponse> => + buildCodecForObject<InvalidPinResponse>() + .property("ec", codecOptional(codecForNumber())) + .property("hint", codecForAny()) + .property("type", codecForConstString("pending")) + .property("addresses_left", codecForNumber()) + .property("pin_transmissions_left", codecForNumber()) + .property("auth_attempts_left", codecForNumber()) + .property("exhausted", codecForBoolean()) + .property("no_challenge", codecForBoolean()) + .build("ChallengerApi.InvalidPinResponse"); + +export const codecForChallengeSolveResponse = + (): Codec<ChallengeSolveResponse> => + buildCodecForUnion<ChallengeSolveResponse>() + .discriminateOn("type") + .alternative("completed", codecForChallengeRedirect()) + .alternative("pending", codecForChallengeInvalidPinResponse()) + .build("ChallengerApi.ChallengeSolveResponse"); + +export const codecForChallengerAuthResponse = + (): Codec<ChallengerAuthResponse> => + buildCodecForObject<ChallengerAuthResponse>() + .property("access_token", codecForString()) + .property("token_type", codecForAny()) + .property("expires_in", codecForNumber()) + .build("ChallengerApi.ChallengerAuthResponse"); + +export const codecForChallengerInfoResponse = + (): Codec<ChallengerInfoResponse> => + buildCodecForObject<ChallengerInfoResponse>() + .property("id", codecForNumber()) + .property("address", codecForAny()) + .property("address_type", codecForString()) + .property("expires", codecForTimestamp) + .build("ChallengerApi.ChallengerInfoResponse"); diff --git a/packages/taler-util/src/types-taler-common.ts b/packages/taler-util/src/types-taler-common.ts @@ -0,0 +1,399 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Type and schema definitions and helpers for the core GNU Taler protocol. + * + * 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 { codecForAmountString } from "./amounts.js"; +import { + Codec, + buildCodecForObject, + codecForAny, + codecForBoolean, + codecForConstString, + codecForList, + codecForMap, + codecForNumber, + codecForString, + codecOptional, +} from "./codec.js"; +import { codecForEither } from "./index.js"; +import { + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForTimestamp, +} from "./time.js"; + +// 64-byte hash code. +export type HashCode = string; + +export type PaytoHash = string; + +export type AmlOfficerPublicKeyP = string; + +// 32-byte hash code. +export type ShortHashCode = string; + +export type SHA256HashCode = ShortHashCode; + +export type SHA512HashCode = HashCode; + +// 32-byte nonce value, must only be used once. +export type CSNonce = string; + +// 32-byte nonce value, must only be used once. +export type RefreshMasterSeed = string; + +// 32-byte value representing a scalar multiplier +// for scalar operations on points on Curve25519. +export type Cs25519Scalar = string; + +/// +/// KEYS +/// + +// 16-byte access token used to authorize access. +export type ClaimToken = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +export type EddsaPublicKey = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +export type EddsaPrivateKey = string; + +// Edx25519 public keys are points on Curve25519 and represented using the +// standard 256 bits Ed25519 compact format converted to Crockford +// Base32. +//export type Edx25519PublicKey = string; + +// Edx25519 private keys are always points on Curve25519 +// and represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +//export type Edx25519PrivateKey = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +export type EcdhePublicKey = string; + +// Point on Curve25519 represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +export type CsRPublic = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +export type EcdhePrivateKey = string; + +export type CoinPublicKey = EddsaPublicKey; + +// RSA public key converted to Crockford Base32. +export type RsaPublicKey = string; + +export type WireTransferIdentifierRawP = string; +// Subset of numbers: Integers in the +// inclusive range 0 .. (2^53 - 1). +export type SafeUint64 = number; + +export type WadId = string; + +export type Timestamp = TalerProtocolTimestamp; + +export type RelativeTime = TalerProtocolDuration; + +/** + * DD51 https://docs.taler.net/design-documents/051-fractional-digits.html + */ +export interface CurrencySpecification { + // Name of the currency. + name: string; + + // how many digits the user may enter after the decimal_separator + num_fractional_input_digits: Integer; + + // Number of fractional digits to render in normal font and size. + num_fractional_normal_digits: Integer; + + // Number of fractional digits to render always, if needed by + // padding with zeros. + num_fractional_trailing_zero_digits: Integer; + + // map of powers of 10 to alternative currency names / symbols, must + // always have an entry under "0" that defines the base name, + // e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC". + // Communicates the currency symbol to be used. + alt_unit_names: { [log10: string]: string }; +} + +export interface InternationalizedString { + [lang_tag: string]: string; +} + +export type RsaPublicKeySring = string; +export type AgeMask = number; + +// The string must be a data URL according to RFC 2397 +// with explicit mediatype and base64 parameters. +// +// data:<mediatype>;base64,<data> +// +// Supported mediatypes are image/jpeg and image/png. +// Invalid strings will be rejected by the wallet. +export type ImageDataUrl = string; + +/** + * 32-byte value representing a point on Curve25519. + */ +export type Cs25519Point = string; + +/** + * Response from the bank. + */ +export class WithdrawOperationStatusResponse { + status: "selected" | "aborted" | "confirmed" | "pending"; + + selection_done: boolean; + + transfer_done: boolean; + + aborted: boolean; + + amount: string | undefined; + + sender_wire?: string; + + suggested_exchange?: string; + + confirm_transfer_url?: string; + + wire_types: string[]; +} + +export type LitAmountString = `${string}:${number}`; + +export type DecimalNumber = string; + +declare const __amount_str: unique symbol; +export type AmountString = + | (string & { [__amount_str]: true }) + | LitAmountString; +// export type AmountString = string; +export type Base32String = string; +export type EddsaSignatureString = string; +export type EddsaPublicKeyString = string; +export type EddsaPrivateKeyString = string; +export type CoinPublicKeyString = string; + +// FIXME: implement this codec +export const codecForURLString = codecForString; +// FIXME: implement this codec +export const codecForLibtoolVersion = codecForString; +// FIXME: implement this codec +export const codecForCurrencyName = codecForString; +// FIXME: implement this codec +export const codecForDecimalNumber = codecForString; + +export const codecForInternationalizedString = + (): Codec<InternationalizedString> => codecForMap(codecForString()); + +export const codecForWithdrawOperationStatusResponse = + (): Codec<WithdrawOperationStatusResponse> => + buildCodecForObject<WithdrawOperationStatusResponse>() + .property( + "status", + codecForEither( + codecForConstString("selected"), + codecForConstString("confirmed"), + codecForConstString("aborted"), + codecForConstString("pending"), + ), + ) + .property("selection_done", codecForBoolean()) + .property("transfer_done", codecForBoolean()) + .property("aborted", codecForBoolean()) + .property("amount", codecOptional(codecForString())) + .property("sender_wire", codecOptional(codecForString())) + .property("suggested_exchange", codecOptional(codecForString())) + .property("confirm_transfer_url", codecOptional(codecForString())) + .property("wire_types", codecForList(codecForString())) + .build("WithdrawOperationStatusResponse"); + +export const codecForCurrencySpecificiation = + (): Codec<CurrencySpecification> => + buildCodecForObject<CurrencySpecification>() + .property("name", codecForString()) + .property("num_fractional_input_digits", codecForNumber()) + .property("num_fractional_normal_digits", codecForNumber()) + .property("num_fractional_trailing_zero_digits", codecForNumber()) + .property("alt_unit_names", codecForMap(codecForString())) + .build("CurrencySpecification"); + +export interface MerchantConfigResponse { + currency: string; + name: string; + version: string; +} + +export const codecForMerchantConfigResponse = + (): Codec<MerchantConfigResponse> => + buildCodecForObject<MerchantConfigResponse>() + .property("currency", codecForString()) + .property("name", codecForString()) + .property("version", codecForString()) + .build("MerchantConfigResponse"); + +export enum ExchangeProtocolVersion { + /** + * Current version supported by the wallet. + */ + V12 = 12, +} + +export enum MerchantProtocolVersion { + /** + * Current version supported by the wallet. + */ + V3 = 3, +} + +export type HashCodeString = string; + +export type WireSalt = string; + +export interface WalletKycUuid { + // UUID that the wallet should use when initiating + // the KYC check. + requirement_row: number; + + // Hash of the payto:// account URI for the wallet. + h_payto: string; +} + +export const codecForWalletKycUuid = (): Codec<WalletKycUuid> => + buildCodecForObject<WalletKycUuid>() + .property("requirement_row", codecForNumber()) + .property("h_payto", codecForString()) + .build("WalletKycUuid"); + +export interface MerchantUsingTemplateDetails { + summary?: string; + amount?: AmountString; +} + +export type Integer = number; + +export interface BankConversionInfoConfig { + // libtool-style representation of the Bank protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the API. + name: "taler-conversion-info"; + + regional_currency: string; + + fiat_currency: string; + + // Currency used by this bank. + regional_currency_specification: CurrencySpecification; + + // External currency used during conversion. + fiat_currency_specification: CurrencySpecification; +} + +export const codecForBankConversionInfoConfig = + (): Codec<BankConversionInfoConfig> => + buildCodecForObject<BankConversionInfoConfig>() + .property("name", codecForConstString("taler-conversion-info")) + .property("version", codecForString()) + .property("fiat_currency", codecForString()) + .property("regional_currency", codecForString()) + .property("fiat_currency_specification", codecForCurrencySpecificiation()) + .property( + "regional_currency_specification", + codecForCurrencySpecificiation(), + ) + .build("BankConversionInfoConfig"); + +export interface DenominationExpiredMessage { + // Taler error code. Note that beyond + // expiration this message format is also + // used if the key is not yet valid, or + // has been revoked. + code: number; + + // Signature by the exchange over a + // TALER_DenominationExpiredAffirmationPS. + // Must have purpose TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED. + exchange_sig: EddsaSignatureString; + + // Public key of the exchange used to create + // the 'exchange_sig. + exchange_pub: EddsaPublicKeyString; + + // Hash of the denomination public key that is unknown. + h_denom_pub: HashCodeString; + + // When was the signature created. + timestamp: TalerProtocolTimestamp; + + // What kind of operation was requested that now + // failed? + oper: string; +} + +export const codecForDenominationExpiredMessage = () => + buildCodecForObject<DenominationExpiredMessage>() + .property("code", codecForNumber()) + .property("exchange_sig", codecForString()) + .property("exchange_pub", codecForString()) + .property("h_denom_pub", codecForString()) + .property("timestamp", codecForTimestamp) + .property("oper", codecForString()) + .build("DenominationExpiredMessage"); + +export interface CoinHistoryResponse { + // Current balance of the coin. + balance: AmountString; + + // Hash of the coin's denomination. + h_denom_pub: HashCodeString; + + // Transaction history for the coin. + history: any[]; +} + +export const codecForCoinHistoryResponse = () => + buildCodecForObject<CoinHistoryResponse>() + .property("balance", codecForAmountString()) + .property("h_denom_pub", codecForString()) + .property("history", codecForAny()) + .build("CoinHistoryResponse"); diff --git a/packages/taler-util/src/types-taler-corebank.ts b/packages/taler-util/src/types-taler-corebank.ts @@ -0,0 +1,914 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + Codec, + buildCodecForObject, + codecForBoolean, + codecForConstString, + codecForString, + codecOptional, +} from "./codec.js"; +import { + buildCodecForUnion, + codecForAmountString, + codecForEither, + codecForList, + codecForNumber, + codecForTalerUriString, + codecForTimestamp, + codecOptionalDefault, +} from "./index.js"; +import { PaytoString, codecForPaytoString } from "./payto.js"; +import { TalerUriString } from "./taleruri.js"; +import { WithdrawalOperationStatus } from "./types-taler-bank-integration.js"; +import { + AmountString, + CurrencySpecification, + DecimalNumber, + Integer, + ShortHashCode, + Timestamp, + codecForCurrencySpecificiation, + codecForDecimalNumber, +} from "./types-taler-common.js"; + +export interface IntegrationConfig { + // libtool-style representation of the Bank protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + currency: string; + + // How the bank SPA should render this currency. + currency_specification: CurrencySpecification; + + // Name of the API. + name: "taler-bank-integration"; +} +export interface Config { + // Name of this API, always "taler-corebank". + name: "libeufin-bank"; + // name: "taler-corebank"; + + // API version in the form $n:$n:$n + version: string; + + // Bank display name to be used in user interfaces. + // For consistency use "Taler Bank" if missing. + // @since v4, will become mandatory in the next version. + bank_name: string; + + // Advertised base URL to use when you sharing an URL with another + // program. + // @since v4. + base_url?: string; + + // If 'true' the server provides local currency conversion support + // If 'false' some parts of the API are not supported and return 501 + allow_conversion: boolean; + + // If 'true' anyone can register + // If 'false' only the admin can + allow_registrations: boolean; + + // If 'true' account can delete themselves + // If 'false' only the admin can delete accounts + allow_deletions: boolean; + + // If 'true' anyone can edit their name + // If 'false' only admin can + allow_edit_name: boolean; + + // If 'true' anyone can edit their cashout account + // If 'false' only the admin + allow_edit_cashout_payto_uri: boolean; + + // Default debt limit for newly created accounts + default_debit_threshold: AmountString; + + // Currency used by this bank. + currency: string; + + // How the bank SPA should render this currency. + currency_specification: CurrencySpecification; + + // TAN channels supported by the server + supported_tan_channels: TanChannel[]; + + // Wire transfer type supported by the bank. + // Default to 'iban' is missing + // @since v4, may become mandatory in the future. + wire_type: string; + + // Wire transfer execution fees. + // @since v4, will become mandatory in the next version. + wire_transfer_fees?: AmountString; +} + +export interface BankAccountCreateWithdrawalRequest { + // Amount to withdraw. If given, the wallet + // cannot change the amount. + // Optional since **vC2EC**. + amount?: AmountString; + + // Suggested amount to withdraw. The wallet can + // still change the suggestion. + // @since **vC2EC** + suggested_amount?: AmountString; +} + +export interface BankAccountCreateWithdrawalResponse { + // ID of the withdrawal, can be used to view/modify the withdrawal operation. + withdrawal_id: string; + + // URI that can be passed to the wallet to initiate the withdrawal. + taler_withdraw_uri: TalerUriString; +} +export interface WithdrawalPublicInfo { + // Current status of the operation + // pending: the operation is pending parameters selection (exchange and reserve public key) + // selected: the operations has been selected and is pending confirmation + // aborted: the operation has been aborted + // confirmed: the transfer has been confirmed and registered by the bank + status: WithdrawalOperationStatus; + + // Amount that will be withdrawn with this operation + // (raw amount without fee considerations). + amount?: AmountString; + + // Suggestion for the amount to be withdrawn with this + // operation. Given if a suggestion was made but the + // user may still change the amount. + // Optional since **vC2EC**. + suggested_amount?: AmountString; + + // Account username + username: string; + + // Reserve public key selected by the exchange, + // only non-null if status is selected or confirmed. + selected_reserve_pub?: string; + + // Exchange account selected by the wallet + // only non-null if status is selected or confirmed. + selected_exchange_account?: PaytoString; +} + +export interface BankAccountTransactionsResponse { + transactions: BankAccountTransactionInfo[]; +} + +export interface BankAccountTransactionInfo { + creditor_payto_uri: PaytoString; + debtor_payto_uri: PaytoString; + + amount: AmountString; + direction: "debit" | "credit"; + + subject: string; + + // Transaction unique ID. Matches + // $transaction_id from the URI. + row_id: number; + date: Timestamp; +} + +export interface CreateTransactionRequest { + // Address in the Payto format of the wire transfer receiver. + // It needs at least the 'message' query string parameter. + payto_uri: PaytoString; + + // Transaction amount (in the $currency:x.y format), optional. + // However, when not given, its value must occupy the 'amount' + // query string parameter of the 'payto' field. In case it + // is given in both places, the paytoUri's takes the precedence. + amount?: AmountString; + + // Nonce to make the request idempotent. Requests with the same + // request_uid that differ in any of the other fields + // are rejected. + // @since v4, will become mandatory in the next version. + request_uid?: ShortHashCode; +} + +export interface CreateTransactionResponse { + // ID identifying the transaction being created + row_id: Integer; +} + +export interface RegisterAccountResponse { + // Internal payto URI of this bank account. + internal_payto_uri: PaytoString; +} + +export interface RegisterAccountRequest { + // Username + username: string; + + // Password. + password: string; + + // Legal name of the account owner + name: string; + + // Defaults to false. + is_public?: boolean; + + // Is this a taler exchange account? + // If true: + // - incoming transactions to the account that do not + // have a valid reserve public key are automatically + // - the account provides the taler-wire-gateway-api endpoints + // Defaults to false. + is_taler_exchange?: boolean; + + // Addresses where to send the TAN for transactions. + contact_data?: ChallengeContactData; + + // 'payto' address of a fiat bank account. + // Payments will be sent to this bank account + // when the user wants to convert the regional currency + // back to fiat currency outside bank. + cashout_payto_uri?: PaytoString; + + // Internal payto URI of this bank account. + // Used mostly for testing. + payto_uri?: PaytoString; + + // If present, set the max debit allowed for this user + // Only admin can set this property. + debit_threshold?: AmountString; + + // If present, set a custom minimum cashout amount for this account. + // Only admin can set this property + // @since v4 + min_cashout?: AmountString; + + // If present, enables 2FA and set the TAN channel used for challenges + // Only admin can set this property, other user can reconfig their account + // after creation. + tan_channel?: TanChannel; +} + +export type EmailAddress = string; +export type PhoneNumber = string; + +export interface ChallengeContactData { + // E-Mail address + email?: EmailAddress; + + // Phone number. + phone?: PhoneNumber; +} + +export interface AccountReconfiguration { + // Addresses where to send the TAN for transactions. + // Currently only used for cashouts. + // If missing, cashouts will fail. + // In the future, might be used for other transactions + // as well. + // Only admin can change this property. + contact_data?: ChallengeContactData; + + // 'payto' URI of a fiat bank account. + // Payments will be sent to this bank account + // when the user wants to convert the regional currency + // back to fiat currency outside bank. + // Only admin can change this property if not allowed in config + cashout_payto_uri?: PaytoString; + + // If present, change the legal name associated with $username. + // Only admin can change this property if not allowed in config + name?: string; + + // Make this account visible to anyone? + is_public?: boolean; + + // If present, change the max debit allowed for this user + // Only admin can change this property. + debit_threshold?: AmountString; + + // If present, change the custom minimum cashout amount for this account. + // Only admin can set this property + // @since v4 + min_cashout?: AmountString; + + // If present, enables 2FA and set the TAN channel used for challenges + tan_channel?: TanChannel | null; +} + +export interface AccountPasswordChange { + // New password. + new_password: string; + // Old password. If present, check that the old password matches. + // Optional for admin account. + old_password?: string; +} + +export interface PublicAccountsResponse { + public_accounts: PublicAccount[]; +} +export interface PublicAccount { + // Username of the account + username: string; + + // Internal payto URI of this bank account. + payto_uri: string; + + // Current balance of the account + balance: Balance; + + // Is this a taler exchange account? + is_taler_exchange: boolean; + + // Opaque unique ID used for pagination. + // @since v4, will become mandatory in the future. + row_id?: Integer; +} + +export interface ListBankAccountsResponse { + accounts: AccountMinimalData[]; +} +export interface Balance { + amount: AmountString; + credit_debit_indicator: "credit" | "debit"; +} +export interface AccountMinimalData { + // Username + username: string; + + // Legal name of the account owner. + name: string; + + // Internal payto URI of this bank account. + payto_uri: PaytoString; + + // current balance of the account + balance: Balance; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: AmountString; + + // Custom minimum cashout amount for this account. + // If null or absent, the global conversion fee is used. + // @since v4 + min_cashout?: AmountString; + + // Is this account visible to anyone? + is_public: boolean; + + // Is this a taler exchange account? + is_taler_exchange: boolean; + + // Opaque unique ID used for pagination. + // @since v4, will become mandatory in the future. + row_id?: Integer; + + // Current status of the account + // active: the account can be used + // deleted: the account has been deleted but is retained for compliance + // reasons, only the administrator can access it + // Default to 'active' is missing + // @since v4, will become mandatory in the next version. + status?: "active" | "deleted"; +} + +export interface AccountData { + // Legal name of the account owner. + name: string; + + // Available balance on the account. + balance: Balance; + + // payto://-URI of the account. + payto_uri: PaytoString; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: AmountString; + + // Custom minimum cashout amount for this account. + // If null or absent, the global conversion fee is used. + // @since v4 + min_cashout?: AmountString; + + contact_data?: ChallengeContactData; + + // 'payto' address pointing the bank account + // where to send cashouts. This field is optional + // because not all the accounts are required to participate + // in the merchants' circuit. One example is the exchange: + // that never cashouts. Registering these accounts can + // be done via the access API. + cashout_payto_uri?: PaytoString; + + // Is this account visible to anyone? + is_public: boolean; + + // Is this a taler exchange account? + is_taler_exchange: boolean; + + // Is 2FA enabled and what channel is used for challenges? + tan_channel?: TanChannel; + + // Current status of the account + // active: the account can be used + // deleted: the account has been deleted but is retained for compliance + // reasons, only the administrator can access it + // Default to 'active' is missing + // @since v4, will become mandatory in the next version. + status?: "active" | "deleted"; +} + +export interface CashoutRequest { + // Nonce to make the request idempotent. Requests with the same + // request_uid that differ in any of the other fields + // are rejected. + request_uid: ShortHashCode; + + // Optional subject to associate to the + // cashout operation. This data will appear + // as the incoming wire transfer subject in + // the user's fiat bank account. + subject?: string; + + // That is the plain amount that the user specified + // to cashout. Its $currency is the (regional) currency of the + // bank instance. + amount_debit: AmountString; + + // That is the amount that will effectively be + // transferred by the bank to the user's bank + // account, that is external to the regional currency. + // It is expressed in the fiat currency and + // is calculated after the cashout fee and the + // exchange rate. See the /cashout-rates call. + // The client needs to calculate this amount + // correctly based on the amount_debit and the cashout rate, + // otherwise the request will fail. + amount_credit: AmountString; +} + +export interface CashoutResponse { + // ID identifying the operation being created + cashout_id: number; +} + +/** + * @deprecated since 4, use 2fa + */ +export interface CashoutConfirmRequest { + // the TAN that confirms $CASHOUT_ID. + tan: string; +} + +export interface Cashouts { + // Every string represents a cash-out operation ID. + cashouts: CashoutInfo[]; +} + +export interface CashoutInfo { + cashout_id: number; +} +export interface GlobalCashouts { + // Every string represents a cash-out operation ID. + cashouts: GlobalCashoutInfo[]; +} +export interface GlobalCashoutInfo { + cashout_id: number; + username: string; +} + +export interface CashoutStatusResponse { + // Amount debited to the internal + // regional currency bank account. + amount_debit: AmountString; + + // Amount credited to the external bank account. + amount_credit: AmountString; + + // Transaction subject. + subject: string; + + // Time when the cashout was created. + creation_time: Timestamp; +} + +export interface ConversionRatesResponse { + // Exchange rate to buy the local currency from the external one + buy_at_ratio: DecimalNumber; + + // Exchange rate to sell the local currency for the external one + sell_at_ratio: DecimalNumber; + + // Fee to subtract after applying the buy ratio. + buy_in_fee: DecimalNumber; + + // Fee to subtract after applying the sell ratio. + sell_out_fee: DecimalNumber; +} + +export enum MonitorTimeframeParam { + hour, + day, + month, + year, + decade, +} + +export type MonitorResponse = MonitorNoConversion | MonitorWithConversion; + +// Monitoring stats when conversion is not supported +export interface MonitorNoConversion { + type: "no-conversions"; + + // How many payments were made to a Taler exchange by another + // bank account. + talerInCount: number; + + // Overall volume that has been paid to a Taler + // exchange by another bank account. + talerInVolume: AmountString; + + // How many payments were made by a Taler exchange to another + // bank account. + talerOutCount: number; + + // Overall volume that has been paid by a Taler + // exchange to another bank account. + talerOutVolume: AmountString; +} +// Monitoring stats when conversion is supported +export interface MonitorWithConversion { + type: "with-conversions"; + + // How many cashin operations were confirmed by a + // wallet owner. Note: wallet owners + // are NOT required to be customers of the libeufin-bank. + cashinCount: number; + + // Overall regional currency that has been paid by the regional admin account + // to regional bank accounts to fulfill all the confirmed cashin operations. + cashinRegionalVolume: AmountString; + + // Overall fiat currency that has been paid to the fiat admin account + // by fiat bank accounts to fulfill all the confirmed cashin operations. + cashinFiatVolume: AmountString; + + // How many cashout operations were confirmed. + cashoutCount: number; + + // Overall regional currency that has been paid to the regional admin account + // by fiat bank accounts to fulfill all the confirmed cashout operations. + cashoutRegionalVolume: AmountString; + + // Overall fiat currency that has been paid by the fiat admin account + // to fiat bank accounts to fulfill all the confirmed cashout operations. + cashoutFiatVolume: AmountString; + + // How many payments were made to a Taler exchange by another + // bank account. + talerInCount: number; + + // Overall volume that has been paid to a Taler + // exchange by another bank account. + talerInVolume: AmountString; + + // How many payments were made by a Taler exchange to another + // bank account. + talerOutCount: number; + + // Overall volume that has been paid by a Taler + // exchange to another bank account. + talerOutVolume: AmountString; +} +export interface TanTransmission { + // Channel of the last successful transmission of the TAN challenge. + tan_channel: TanChannel; + + // Info of the last successful transmission of the TAN challenge. + tan_info: string; +} + +export interface Challenge { + // Unique identifier of the challenge to solve to run this protected + // operation. + challenge_id: number; +} + +export interface ChallengeSolve { + // The TAN code that solves $CHALLENGE_ID + tan: string; +} + +export enum TanChannel { + SMS = "sms", + EMAIL = "email", +} + +export const codecForIntegrationBankConfig = (): Codec<IntegrationConfig> => + buildCodecForObject<IntegrationConfig>() + .property("name", codecForConstString("taler-bank-integration")) + .property("version", codecForString()) + .property("currency", codecForString()) + .property("currency_specification", codecForCurrencySpecificiation()) + .build("TalerCorebankApi.IntegrationConfig"); + +export const codecForCoreBankConfig = (): Codec<Config> => + buildCodecForObject<Config>() + .property("name", codecForConstString("libeufin-bank")) + .property("version", codecForString()) + .property("bank_name", codecForString()) + .property("base_url", codecOptional(codecForString())) + .property("allow_conversion", codecForBoolean()) + .property("allow_registrations", codecForBoolean()) + .property("allow_deletions", codecForBoolean()) + .property("allow_edit_name", codecForBoolean()) + .property("allow_edit_cashout_payto_uri", codecForBoolean()) + .property("default_debit_threshold", codecForAmountString()) + .property("currency", codecForString()) + .property("currency_specification", codecForCurrencySpecificiation()) + .property( + "supported_tan_channels", + codecForList( + codecForEither( + codecForConstString(TanChannel.SMS), + codecForConstString(TanChannel.EMAIL), + ), + ), + ) + .property("wire_type", codecOptionalDefault(codecForString(), "iban")) + .property("wire_transfer_fees", codecOptional(codecForAmountString())) + .build("TalerCorebankApi.Config"); + +const codecForBalance = (): Codec<Balance> => + buildCodecForObject<Balance>() + .property("amount", codecForAmountString()) + .property( + "credit_debit_indicator", + codecForEither( + codecForConstString("credit"), + codecForConstString("debit"), + ), + ) + .build("TalerCorebankApi.Balance"); + +const codecForPublicAccount = (): Codec<PublicAccount> => + buildCodecForObject<PublicAccount>() + .property("username", codecForString()) + .property("balance", codecForBalance()) + .property("payto_uri", codecForPaytoString()) + .property("is_taler_exchange", codecForBoolean()) + .property("row_id", codecOptional(codecForNumber())) + .build("TalerCorebankApi.PublicAccount"); + +export const codecForPublicAccountsResponse = + (): Codec<PublicAccountsResponse> => + buildCodecForObject<PublicAccountsResponse>() + .property("public_accounts", codecForList(codecForPublicAccount())) + .build("TalerCorebankApi.PublicAccountsResponse"); + +export const codecForAccountMinimalData = + (): Codec<AccountMinimalData> => + buildCodecForObject<AccountMinimalData>() + .property("username", codecForString()) + .property("name", codecForString()) + .property("payto_uri", codecForPaytoString()) + .property("balance", codecForBalance()) + .property("row_id", codecForNumber()) + .property("debit_threshold", codecForAmountString()) + .property("min_cashout", codecOptional(codecForAmountString())) + .property("is_public", codecForBoolean()) + .property("is_taler_exchange", codecForBoolean()) + .property( + "status", + codecOptional( + codecForEither( + codecForConstString("active"), + codecForConstString("deleted"), + ), + ), + ) + .build("TalerCorebankApi.AccountMinimalData"); + +export const codecForListBankAccountsResponse = + (): Codec<ListBankAccountsResponse> => + buildCodecForObject<ListBankAccountsResponse>() + .property("accounts", codecForList(codecForAccountMinimalData())) + .build("TalerCorebankApi.ListBankAccountsResponse"); + +export const codecForAccountData = (): Codec<AccountData> => + buildCodecForObject<AccountData>() + .property("name", codecForString()) + .property("balance", codecForBalance()) + .property("payto_uri", codecForPaytoString()) + .property("debit_threshold", codecForAmountString()) + .property("min_cashout", codecOptional(codecForAmountString())) + .property("contact_data", codecOptional(codecForChallengeContactData())) + .property("cashout_payto_uri", codecOptional(codecForPaytoString())) + .property("is_public", codecForBoolean()) + .property("is_taler_exchange", codecForBoolean()) + .property( + "tan_channel", + codecOptional( + codecForEither( + codecForConstString(TanChannel.SMS), + codecForConstString(TanChannel.EMAIL), + ), + ), + ) + .property( + "status", + codecOptional( + codecForEither( + codecForConstString("active"), + codecForConstString("deleted"), + ), + ), + ) + .build("TalerCorebankApi.AccountData"); + +export const codecForChallengeContactData = + (): Codec<ChallengeContactData> => + buildCodecForObject<ChallengeContactData>() + .property("email", codecOptional(codecForString())) + .property("phone", codecOptional(codecForString())) + .build("TalerCorebankApi.ChallengeContactData"); + +export const codecForWithdrawalPublicInfo = + (): Codec<WithdrawalPublicInfo> => + buildCodecForObject<WithdrawalPublicInfo>() + .property( + "status", + codecForEither( + codecForConstString("pending"), + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) + .property("amount", codecOptional(codecForAmountString())) + .property("username", codecForString()) + .property("selected_reserve_pub", codecOptional(codecForString())) + .property( + "selected_exchange_account", + codecOptional(codecForPaytoString()), + ) + .build("TalerCorebankApi.WithdrawalPublicInfo"); + +export const codecForBankAccountTransactionsResponse = + (): Codec<BankAccountTransactionsResponse> => + buildCodecForObject<BankAccountTransactionsResponse>() + .property( + "transactions", + codecForList(codecForBankAccountTransactionInfo()), + ) + .build("TalerCorebankApi.BankAccountTransactionsResponse"); + +export const codecForBankAccountTransactionInfo = + (): Codec<BankAccountTransactionInfo> => + buildCodecForObject<BankAccountTransactionInfo>() + .property("creditor_payto_uri", codecForPaytoString()) + .property("debtor_payto_uri", codecForPaytoString()) + .property("amount", codecForAmountString()) + .property( + "direction", + codecForEither( + codecForConstString("debit"), + codecForConstString("credit"), + ), + ) + .property("subject", codecForString()) + .property("row_id", codecForNumber()) + .property("date", codecForTimestamp) + .build("TalerCorebankApi.BankAccountTransactionInfo"); + +export const codecForCreateTransactionResponse = + (): Codec<CreateTransactionResponse> => + buildCodecForObject<CreateTransactionResponse>() + .property("row_id", codecForNumber()) + .build("TalerCorebankApi.CreateTransactionResponse"); + +export const codecForRegisterAccountResponse = + (): Codec<RegisterAccountResponse> => + buildCodecForObject<RegisterAccountResponse>() + .property("internal_payto_uri", codecForPaytoString()) + .build("TalerCorebankApi.RegisterAccountResponse"); + +export const codecForBankAccountCreateWithdrawalResponse = + (): Codec<BankAccountCreateWithdrawalResponse> => + buildCodecForObject<BankAccountCreateWithdrawalResponse>() + .property("taler_withdraw_uri", codecForTalerUriString()) + .property("withdrawal_id", codecForString()) + .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse"); + +export const codecForCashoutPending = + (): Codec<CashoutResponse> => + buildCodecForObject<CashoutResponse>() + .property("cashout_id", codecForNumber()) + .build("TalerCorebankApi.CashoutPending"); + +export const codecForCashouts = (): Codec<Cashouts> => + buildCodecForObject<Cashouts>() + .property("cashouts", codecForList(codecForCashoutInfo())) + .build("TalerCorebankApi.Cashouts"); + +export const codecForCashoutInfo = (): Codec<CashoutInfo> => + buildCodecForObject<CashoutInfo>() + .property("cashout_id", codecForNumber()) + .build("TalerCorebankApi.CashoutInfo"); + +export const codecForGlobalCashouts = + (): Codec<GlobalCashouts> => + buildCodecForObject<GlobalCashouts>() + .property("cashouts", codecForList(codecForGlobalCashoutInfo())) + .build("TalerCorebankApi.GlobalCashouts"); + +export const codecForGlobalCashoutInfo = + (): Codec<GlobalCashoutInfo> => + buildCodecForObject<GlobalCashoutInfo>() + .property("cashout_id", codecForNumber()) + .property("username", codecForString()) + .build("TalerCorebankApi.GlobalCashoutInfo"); + +export const codecForCashoutStatusResponse = + (): Codec<CashoutStatusResponse> => + buildCodecForObject<CashoutStatusResponse>() + .property("amount_debit", codecForAmountString()) + .property("amount_credit", codecForAmountString()) + .property("subject", codecForString()) + .property("creation_time", codecForTimestamp) + .build("TalerCorebankApi.CashoutStatusResponse"); + +export const codecForConversionRatesResponse = + (): Codec<ConversionRatesResponse> => + buildCodecForObject<ConversionRatesResponse>() + .property("buy_at_ratio", codecForDecimalNumber()) + .property("buy_in_fee", codecForDecimalNumber()) + .property("sell_at_ratio", codecForDecimalNumber()) + .property("sell_out_fee", codecForDecimalNumber()) + .build("TalerCorebankApi.ConversionRatesResponse"); + +export const codecForMonitorResponse = (): Codec<MonitorResponse> => + buildCodecForUnion<MonitorResponse>() + .discriminateOn("type") + .alternative("no-conversions", codecForMonitorNoConversion()) + .alternative("with-conversions", codecForMonitorWithCashout()) + .build("TalerWireGatewayApi.IncomingBankTransaction"); + +export const codecForMonitorNoConversion = (): Codec<MonitorNoConversion> => + buildCodecForObject<MonitorNoConversion>() + .property("type", codecForConstString("no-conversions")) + .property("talerInCount", codecForNumber()) + .property("talerInVolume", codecForAmountString()) + .property("talerOutCount", codecForNumber()) + .property("talerOutVolume", codecForAmountString()) + .build("TalerCorebankApi.MonitorJustPayouts"); + +export const codecForMonitorWithCashout = (): Codec<MonitorWithConversion> => + buildCodecForObject<MonitorWithConversion>() + .property("type", codecForConstString("with-conversions")) + .property("cashinCount", codecForNumber()) + .property("cashinFiatVolume", codecForAmountString()) + .property("cashinRegionalVolume", codecForAmountString()) + .property("cashoutCount", codecForNumber()) + .property("cashoutFiatVolume", codecForAmountString()) + .property("cashoutRegionalVolume", codecForAmountString()) + .property("talerInCount", codecForNumber()) + .property("talerInVolume", codecForAmountString()) + .property("talerOutCount", codecForNumber()) + .property("talerOutVolume", codecForAmountString()) + .build("TalerCorebankApi.MonitorWithCashout"); + +export const codecForChallenge = (): Codec<Challenge> => + buildCodecForObject<Challenge>() + .property("challenge_id", codecForNumber()) + .build("TalerCorebankApi.Challenge"); + +export const codecForTanTransmission = (): Codec<TanTransmission> => + buildCodecForObject<TanTransmission>() + .property( + "tan_channel", + codecForEither( + codecForConstString(TanChannel.SMS), + codecForConstString(TanChannel.EMAIL), + ), + ) + .property("tan_info", codecForString()) + .build("TalerCorebankApi.TanTransmission"); diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts @@ -0,0 +1,1415 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + Codec, + buildCodecForObject, + codecForAny, + codecForList, + codecForNumber, + codecForString, + codecForStringURL, + codecOptional, +} from "./codec.js"; +import { + buildCodecForUnion, + codecForAmountString, + codecForBoolean, + codecForConstString, + codecForCurrencySpecificiation, + codecForMap, + strcmp, +} from "./index.js"; +import { Edx25519PublicKeyEnc } from "./taler-crypto.js"; +import { + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForDuration, + codecForTimestamp, +} from "./time.js"; +import { + AgeMask, + AmountString, + Base32String, + CoinPublicKeyString, + Cs25519Point, + CurrencySpecification, + EddsaPublicKeyString, + EddsaSignatureString, + HashCodeString, + InternationalizedString, + RsaPublicKeySring, + WireSalt, +} from "./types-taler-common.js"; + +export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; + +export interface RsaDenominationPubKey { + readonly cipher: DenomKeyType.Rsa; + readonly rsa_public_key: string; + readonly age_mask: number; +} + +export interface CsDenominationPubKey { + readonly cipher: DenomKeyType.ClauseSchnorr; + readonly age_mask: number; + readonly cs_public_key: string; +} + +export namespace DenominationPubKey { + export function cmp( + p1: DenominationPubKey, + p2: DenominationPubKey, + ): -1 | 0 | 1 { + if (p1.cipher < p2.cipher) { + return -1; + } else if (p1.cipher > p2.cipher) { + return +1; + } else if ( + p1.cipher === DenomKeyType.Rsa && + p2.cipher === DenomKeyType.Rsa + ) { + if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { + return -1; + } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { + return 1; + } + return strcmp(p1.rsa_public_key, p2.rsa_public_key); + } else if ( + p1.cipher === DenomKeyType.ClauseSchnorr && + p2.cipher === DenomKeyType.ClauseSchnorr + ) { + if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { + return -1; + } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { + return 1; + } + return strcmp(p1.cs_public_key, p2.cs_public_key); + } else { + throw Error("unsupported cipher"); + } + } +} + +export const codecForRsaDenominationPubKey = () => + buildCodecForObject<RsaDenominationPubKey>() + .property("cipher", codecForConstString(DenomKeyType.Rsa)) + .property("rsa_public_key", codecForString()) + .property("age_mask", codecForNumber()) + .build("DenominationPubKey"); + +export const codecForCsDenominationPubKey = () => + buildCodecForObject<CsDenominationPubKey>() + .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr)) + .property("cs_public_key", codecForString()) + .property("age_mask", codecForNumber()) + .build("CsDenominationPubKey"); + +export const codecForDenominationPubKey = () => + buildCodecForUnion<DenominationPubKey>() + .discriminateOn("cipher") + .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey()) + .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) + .build("DenominationPubKey"); + +/** + * Denomination as found in the /keys response from the exchange. + */ +export class ExchangeDenomination { + /** + * Value of one coin of the denomination. + */ + value: string; + + /** + * Public signing key of the denomination. + */ + denom_pub: DenominationPubKey; + + /** + * Fee for withdrawing. + */ + fee_withdraw: string; + + /** + * Fee for depositing. + */ + fee_deposit: string; + + /** + * Fee for refreshing. + */ + fee_refresh: string; + + /** + * Fee for refunding. + */ + fee_refund: string; + + /** + * Start date from which withdraw is allowed. + */ + stamp_start: TalerProtocolTimestamp; + + /** + * End date for withdrawing. + */ + stamp_expire_withdraw: TalerProtocolTimestamp; + + /** + * Expiration date after which the exchange can forget about + * the currency. + */ + stamp_expire_legal: TalerProtocolTimestamp; + + /** + * Date after which the coins of this denomination can't be + * deposited anymore. + */ + stamp_expire_deposit: TalerProtocolTimestamp; + + /** + * Signature over the denomination information by the exchange's master + * signing key. + */ + master_sig: string; +} + +/** + * Signature by the auditor that a particular denomination key is audited. + */ +export class AuditorDenomSig { + /** + * Denomination public key's hash. + */ + denom_pub_h: string; + + /** + * The signature. + */ + auditor_sig: string; +} + +/** + * Auditor information as given by the exchange in /keys. + */ +export class ExchangeAuditor { + /** + * Auditor's public key. + */ + auditor_pub: string; + + /** + * Base URL of the auditor. + */ + auditor_url: string; + + /** + * List of signatures for denominations by the auditor. + */ + denomination_keys: AuditorDenomSig[]; +} + +export type ExchangeWithdrawValue = + | ExchangeRsaWithdrawValue + | ExchangeCsWithdrawValue; + +export interface ExchangeRsaWithdrawValue { + cipher: "RSA"; +} + +export interface ExchangeCsWithdrawValue { + cipher: "CS"; + + /** + * CSR R0 value + */ + r_pub_0: string; + + /** + * CSR R1 value + */ + r_pub_1: string; +} + +export interface RecoupRequest { + /** + * Hashed denomination public key of the coin we want to get + * paid back. + */ + denom_pub_hash: string; + + /** + * Signature over the coin public key by the denomination. + * + * The string variant is for the legacy exchange protocol. + */ + denom_sig: UnblindedSignature; + + /** + * Blinding key that was used during withdraw, + * used to prove that we were actually withdrawing the coin. + */ + coin_blind_key_secret: string; + + /** + * Signature of TALER_RecoupRequestPS created with the coin's private key. + */ + coin_sig: string; + + ewv: ExchangeWithdrawValue; +} + +export interface RecoupRefreshRequest { + /** + * Hashed enomination public key of the coin we want to get + * paid back. + */ + denom_pub_hash: string; + + /** + * Signature over the coin public key by the denomination. + * + * The string variant is for the legacy exchange protocol. + */ + denom_sig: UnblindedSignature; + + /** + * Coin's blinding factor. + */ + coin_blind_key_secret: string; + + /** + * Signature of TALER_RecoupRefreshRequestPS created with + * the coin's private key. + */ + coin_sig: string; + + ewv: ExchangeWithdrawValue; +} + +/** + * Response that we get from the exchange for a payback request. + */ +export interface RecoupConfirmation { + /** + * Public key of the reserve that will receive the payback. + */ + reserve_pub?: string; + + /** + * Public key of the old coin that will receive the recoup, + * provided if refreshed was true. + */ + old_coin_pub?: string; +} + +export type UnblindedSignature = RsaUnblindedSignature; + +export interface RsaUnblindedSignature { + cipher: DenomKeyType.Rsa; + rsa_signature: string; +} + +/** + * Deposit permission for a single coin. + */ +export interface CoinDepositPermission { + /** + * Signature by the coin. + */ + coin_sig: string; + + /** + * Public key of the coin being spend. + */ + coin_pub: string; + + /** + * Signature made by the denomination public key. + * + * The string variant is for legacy protocol support. + */ + + ub_sig: UnblindedSignature; + + /** + * The denomination public key associated with this coin. + */ + h_denom: string; + + /** + * The amount that is subtracted from this coin with this payment. + */ + contribution: string; + + /** + * URL of the exchange this coin was withdrawn from. + */ + exchange_url: string; + + minimum_age_sig?: EddsaSignatureString; + + age_commitment?: Edx25519PublicKeyEnc[]; + + h_age_commitment?: string; +} + +/** + * Element of the payback list that the + * exchange gives us in /keys. + */ +export class Recoup { + /** + * The hash of the denomination public key for which the payback is offered. + */ + h_denom_pub: string; +} + +/** + * Structure that the exchange gives us in /keys. + */ +export class ExchangeKeysJson { + /** + * Canonical, public base URL of the exchange. + */ + base_url: string; + + currency: string; + + currency_specification?: CurrencySpecification; + + /** + * The exchange's master public key. + */ + master_public_key: string; + + /** + * The list of auditors (partially) auditing the exchange. + */ + auditors: ExchangeAuditor[]; + + /** + * Timestamp when this response was issued. + */ + list_issue_date: TalerProtocolTimestamp; + + /** + * List of revoked denominations. + */ + recoup?: Recoup[]; + + /** + * Short-lived signing keys used to sign online + * responses. + */ + signkeys: ExchangeSignKeyJson[]; + + /** + * Protocol version. + */ + version: string; + + reserve_closing_delay: TalerProtocolDuration; + + global_fees: GlobalFees[]; + + accounts: ExchangeWireAccount[]; + + wire_fees: { [methodName: string]: WireFeesJson[] }; + + denominations: DenomGroup[]; +} + +export interface ExchangeMeltRequest { + coin_pub: CoinPublicKeyString; + confirm_sig: EddsaSignatureString; + denom_pub_hash: HashCodeString; + denom_sig: UnblindedSignature; + rc: string; + value_with_fee: AmountString; + age_commitment_hash?: HashCodeString; +} + +export interface ExchangeMeltResponse { + /** + * Which of the kappa indices does the client not have to reveal. + */ + noreveal_index: number; + + /** + * Signature of TALER_RefreshMeltConfirmationPS whereby the exchange + * affirms the successful melt and confirming the noreveal_index + */ + exchange_sig: EddsaSignatureString; + + /* + * public EdDSA key of the exchange that was used to generate the signature. + * Should match one of the exchange's signing keys from /keys. Again given + * explicitly as the client might otherwise be confused by clock skew as to + * which signing key was used. + */ + exchange_pub: EddsaPublicKeyString; + + /* + * Base URL to use for operations on the refresh context + * (so the reveal operation). If not given, + * the base URL is the same as the one used for this request. + * Can be used if the base URL for /refreshes/ differs from that + * for /coins/, i.e. for load balancing. Clients SHOULD + * respect the refresh_base_url if provided. Any HTTP server + * belonging to an exchange MUST generate a 307 or 308 redirection + * to the correct base URL should a client uses the wrong base + * URL, or if the base URL has changed since the melt. + * + * When melting the same coin twice (technically allowed + * as the response might have been lost on the network), + * the exchange may return different values for the refresh_base_url. + */ + refresh_base_url?: string; +} + +export interface ExchangeRevealItem { + ev_sig: BlindedDenominationSignature; +} + +export interface ExchangeRevealResponse { + // List of the exchange's blinded RSA signatures on the new coins. + ev_sigs: ExchangeRevealItem[]; +} + +export const codecForDenomination = (): Codec<ExchangeDenomination> => + buildCodecForObject<ExchangeDenomination>() + .property("value", codecForString()) + .property("denom_pub", codecForDenominationPubKey()) + .property("fee_withdraw", codecForString()) + .property("fee_deposit", codecForString()) + .property("fee_refresh", codecForString()) + .property("fee_refund", codecForString()) + .property("stamp_start", codecForTimestamp) + .property("stamp_expire_withdraw", codecForTimestamp) + .property("stamp_expire_legal", codecForTimestamp) + .property("stamp_expire_deposit", codecForTimestamp) + .property("master_sig", codecForString()) + .build("Denomination"); + +export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => + buildCodecForObject<AuditorDenomSig>() + .property("denom_pub_h", codecForString()) + .property("auditor_sig", codecForString()) + .build("AuditorDenomSig"); + +export const codecForAuditor = (): Codec<ExchangeAuditor> => + buildCodecForObject<ExchangeAuditor>() + .property("auditor_pub", codecForString()) + .property("auditor_url", codecForString()) + .property("denomination_keys", codecForList(codecForAuditorDenomSig())) + .build("Auditor"); + +/** + * Structure of one exchange signing key in the /keys response. + */ +export class ExchangeSignKeyJson { + stamp_start: TalerProtocolTimestamp; + stamp_expire: TalerProtocolTimestamp; + stamp_end: TalerProtocolTimestamp; + key: EddsaPublicKeyString; + master_sig: EddsaSignatureString; +} + +export type DenomGroup = + | DenomGroupRsa + | DenomGroupCs + | DenomGroupRsaAgeRestricted + | DenomGroupCsAgeRestricted; + +export interface DenomGroupCommon { + // How much are coins of this denomination worth? + value: AmountString; + + // Fee charged by the exchange for withdrawing a coin of this denomination. + fee_withdraw: AmountString; + + // Fee charged by the exchange for depositing a coin of this denomination. + fee_deposit: AmountString; + + // Fee charged by the exchange for refreshing a coin of this denomination. + fee_refresh: AmountString; + + // Fee charged by the exchange for refunding a coin of this denomination. + fee_refund: AmountString; + + // XOR of all the SHA-512 hash values of the denominations' public keys + // in this group. Note that for hashing, the binary format of the + // public keys is used, and not their base32 encoding. + hash: HashCodeString; +} + +export interface DenomCommon { + // Signature of TALER_DenominationKeyValidityPS. + master_sig: EddsaSignatureString; + + // When does the denomination key become valid? + stamp_start: TalerProtocolTimestamp; + + // When is it no longer possible to deposit coins + // of this denomination? + stamp_expire_withdraw: TalerProtocolTimestamp; + + // Timestamp indicating by when legal disputes relating to these coins must + // be settled, as the exchange will afterwards destroy its evidence relating to + // transactions involving this coin. + stamp_expire_legal: TalerProtocolTimestamp; + + stamp_expire_deposit: TalerProtocolTimestamp; + + // Set to 'true' if the exchange somehow "lost" + // the private key. The denomination was not + // necessarily revoked, but still cannot be used + // to withdraw coins at this time (theoretically, + // the private key could be recovered in the + // future; coins signed with the private key + // remain valid). + lost?: boolean; +} + +export interface DenomGroupRsa extends DenomGroupCommon { + cipher: "RSA"; + + denoms: ({ + rsa_pub: RsaPublicKeySring; + } & DenomCommon)[]; +} + +export interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { + cipher: "RSA+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + rsa_pub: RsaPublicKeySring; + } & DenomCommon)[]; +} + +export interface DenomGroupCs extends DenomGroupCommon { + cipher: "CS"; + age_mask: AgeMask; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; +} + +export interface DenomGroupCsAgeRestricted extends DenomGroupCommon { + cipher: "CS+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; +} + +export interface GlobalFees { + // What date (inclusive) does these fees go into effect? + start_date: TalerProtocolTimestamp; + + // What date (exclusive) does this fees stop going into effect? + end_date: TalerProtocolTimestamp; + + // Account history fee, charged when a user wants to + // obtain a reserve/account history. + history_fee: AmountString; + + // Annual fee charged for having an open account at the + // exchange. Charged to the account. If the account + // balance is insufficient to cover this fee, the account + // is automatically deleted/closed. (Note that the exchange + // will keep the account history around for longer for + // regulatory reasons.) + account_fee: AmountString; + + // Purse fee, charged only if a purse is abandoned + // and was not covered by the account limit. + purse_fee: AmountString; + + // How long will the exchange preserve the account history? + // After an account was deleted/closed, the exchange will + // retain the account history for legal reasons until this time. + history_expiration: TalerProtocolDuration; + + // Non-negative number of concurrent purses that any + // account holder is allowed to create without having + // to pay the purse_fee. + purse_account_limit: number; + + // How long does an exchange keep a purse around after a purse + // has expired (or been successfully merged)? A 'GET' request + // for a purse will succeed until the purse expiration time + // plus this value. + purse_timeout: TalerProtocolDuration; + + // Signature of TALER_GlobalFeesPS. + master_sig: string; +} +/** + * Wire fees as announced by the exchange. + */ +export class WireFeesJson { + /** + * Cost of a wire transfer. + */ + wire_fee: string; + + /** + * Cost of clising a reserve. + */ + closing_fee: string; + + /** + * Signature made with the exchange's master key. + */ + sig: string; + + /** + * Date from which the fee applies. + */ + start_date: TalerProtocolTimestamp; + + /** + * Data after which the fee doesn't apply anymore. + */ + end_date: TalerProtocolTimestamp; +} + +export interface ExchangeWireAccount { + // payto:// URI identifying the account and wire method + payto_uri: string; + + // URI to convert amounts from or to the currency used by + // this wire account of the exchange. Missing if no + // conversion is applicable. + conversion_url?: string; + + // Restrictions that apply to bank accounts that would send + // funds to the exchange (crediting this exchange bank account). + // Optional, empty array for unrestricted. + credit_restrictions: AccountRestriction[]; + + // Restrictions that apply to bank accounts that would receive + // funds from the exchange (debiting this exchange bank account). + // Optional, empty array for unrestricted. + debit_restrictions: AccountRestriction[]; + + // Signature using the exchange's offline key over + // a TALER_MasterWireDetailsPS + // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. + master_sig: EddsaSignatureString; + + // Display label wallets should use to show this + // bank account. + // Since protocol **v19**. + bank_label?: string; + priority?: number; +} + +export const codecForExchangeWireAccount = (): Codec<ExchangeWireAccount> => + buildCodecForObject<ExchangeWireAccount>() + .property("conversion_url", codecOptional(codecForStringURL())) + .property("credit_restrictions", codecForList(codecForAny())) + .property("debit_restrictions", codecForList(codecForAny())) + .property("master_sig", codecForString()) + .property("payto_uri", codecForString()) + .property("bank_label", codecOptional(codecForString())) + .property("priority", codecOptional(codecForNumber())) + .build("WireAccount"); + +export interface ExchangeRefundRequest { + // 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: AmountString; + + // SHA-512 hash of the contact of the merchant with the customer. + h_contract_terms: HashCodeString; + + // 64-bit transaction id of the refund transaction between merchant and customer. + rtransaction_id: number; + + // EdDSA public key of the merchant. + merchant_pub: EddsaPublicKeyString; + + // EdDSA signature of the merchant over a + // TALER_RefundRequestPS with purpose + // TALER_SIGNATURE_MERCHANT_REFUND + // affirming the refund. + merchant_sig: EddsaPublicKeyString; +} + +export interface ExchangeRefundSuccessResponse { + // The EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND over + // a TALER_RecoupRefreshConfirmationPS + // using a current signing key of the + // exchange affirming the successful refund. + exchange_sig: EddsaSignatureString; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; +} + +export const codecForExchangeRefundSuccessResponse = + (): Codec<ExchangeRefundSuccessResponse> => + buildCodecForObject<ExchangeRefundSuccessResponse>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .build("ExchangeRefundSuccessResponse"); + +export type AccountRestriction = + | RegexAccountRestriction + | DenyAllAccountRestriction; + +export interface DenyAllAccountRestriction { + type: "deny"; +} + +// Accounts interacting with this type of account +// restriction must have a payto://-URI matching +// the given regex. +export interface RegexAccountRestriction { + type: "regex"; + + // Regular expression that the payto://-URI of the + // partner account must follow. The regular expression + // should follow posix-egrep, but without support for character + // classes, GNU extensions, back-references or intervals. See + // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html + // for a description of the posix-egrep syntax. Applications + // may support regexes with additional features, but exchanges + // must not use such regexes. + payto_regex: string; + + // Hint for a human to understand the restriction + // (that is hopefully easier to comprehend than the regex itself). + human_hint: string; + + // Map from IETF BCP 47 language tags to localized + // human hints. + human_hint_i18n?: InternationalizedString; +} + +export type CoinEnvelope = CoinEnvelopeRsa | CoinEnvelopeCs; + +export interface CoinEnvelopeRsa { + cipher: DenomKeyType.Rsa; + rsa_blinded_planchet: string; +} + +export interface CoinEnvelopeCs { + cipher: DenomKeyType.ClauseSchnorr; + // FIXME: add remaining fields +} + +export interface ExchangeWithdrawRequest { + denom_pub_hash: HashCodeString; + reserve_sig: EddsaSignatureString; + coin_ev: CoinEnvelope; +} + +export interface ExchangeBatchWithdrawRequest { + planchets: ExchangeWithdrawRequest[]; +} + +export interface ExchangeRefreshRevealRequest { + new_denoms_h: HashCodeString[]; + coin_evs: CoinEnvelope[]; + /** + * kappa - 1 transfer private keys (ephemeral ECDHE keys). + */ + transfer_privs: string[]; + + transfer_pub: EddsaPublicKeyString; + + link_sigs: EddsaSignatureString[]; + + /** + * Iff the corresponding denomination has support for age restriction, + * the client MUST provide the original age commitment, i.e. the vector + * of public keys. + */ + old_age_commitment?: Edx25519PublicKeyEnc[]; +} + +export const codecForRecoup = (): Codec<Recoup> => + buildCodecForObject<Recoup>() + .property("h_denom_pub", codecForString()) + .build("Recoup"); + +export const codecForExchangeSigningKey = (): Codec<ExchangeSignKeyJson> => + buildCodecForObject<ExchangeSignKeyJson>() + .property("key", codecForString()) + .property("master_sig", codecForString()) + .property("stamp_end", codecForTimestamp) + .property("stamp_start", codecForTimestamp) + .property("stamp_expire", codecForTimestamp) + .build("ExchangeSignKeyJson"); + +export const codecForGlobalFees = (): Codec<GlobalFees> => + buildCodecForObject<GlobalFees>() + .property("start_date", codecForTimestamp) + .property("end_date", codecForTimestamp) + .property("history_fee", codecForAmountString()) + .property("account_fee", codecForAmountString()) + .property("purse_fee", codecForAmountString()) + .property("history_expiration", codecForDuration) + .property("purse_account_limit", codecForNumber()) + .property("purse_timeout", codecForDuration) + .property("master_sig", codecForString()) + .build("GlobalFees"); + +// FIXME: Validate properly! +export const codecForNgDenominations: Codec<DenomGroup> = codecForAny(); + +export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => + buildCodecForObject<ExchangeKeysJson>() + .property("base_url", codecForString()) + .property("currency", codecForString()) + .property( + "currency_specification", + codecOptional(codecForCurrencySpecificiation()), + ) + .property("master_public_key", codecForString()) + .property("auditors", codecForList(codecForAuditor())) + .property("list_issue_date", codecForTimestamp) + .property("recoup", codecOptional(codecForList(codecForRecoup()))) + .property("signkeys", codecForList(codecForExchangeSigningKey())) + .property("version", codecForString()) + .property("reserve_closing_delay", codecForDuration) + .property("global_fees", codecForList(codecForGlobalFees())) + .property("accounts", codecForList(codecForExchangeWireAccount())) + .property("wire_fees", codecForMap(codecForList(codecForWireFeesJson()))) + .property("denominations", codecForList(codecForNgDenominations)) + .build("ExchangeKeysJson"); + +export const codecForWireFeesJson = (): Codec<WireFeesJson> => + buildCodecForObject<WireFeesJson>() + .property("wire_fee", codecForString()) + .property("closing_fee", codecForString()) + .property("sig", codecForString()) + .property("start_date", codecForTimestamp) + .property("end_date", codecForTimestamp) + .build("WireFeesJson"); + +export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> => + buildCodecForObject<RecoupConfirmation>() + .property("reserve_pub", codecOptional(codecForString())) + .property("old_coin_pub", codecOptional(codecForString())) + .build("RecoupConfirmation"); + +export const codecForWithdrawResponse = (): Codec<ExchangeWithdrawResponse> => + buildCodecForObject<ExchangeWithdrawResponse>() + .property("ev_sig", codecForBlindedDenominationSignature()) + .build("WithdrawResponse"); + +export class ExchangeWithdrawResponse { + ev_sig: BlindedDenominationSignature; +} + +export class ExchangeWithdrawBatchResponse { + ev_sigs: ExchangeWithdrawResponse[]; +} + +export enum DenomKeyType { + Rsa = "RSA", + ClauseSchnorr = "CS", +} + +export namespace DenomKeyType { + export function toIntTag(t: DenomKeyType): number { + switch (t) { + case DenomKeyType.Rsa: + return 1; + case DenomKeyType.ClauseSchnorr: + return 2; + } + } +} + +export interface RsaBlindedDenominationSignature { + cipher: DenomKeyType.Rsa; + blinded_rsa_signature: string; +} + +export interface CSBlindedDenominationSignature { + cipher: DenomKeyType.ClauseSchnorr; +} + +export type BlindedDenominationSignature = + | RsaBlindedDenominationSignature + | CSBlindedDenominationSignature; + +export const codecForRsaBlindedDenominationSignature = () => + buildCodecForObject<RsaBlindedDenominationSignature>() + .property("cipher", codecForConstString(DenomKeyType.Rsa)) + .property("blinded_rsa_signature", codecForString()) + .build("RsaBlindedDenominationSignature"); + +export const codecForBlindedDenominationSignature = () => + buildCodecForUnion<BlindedDenominationSignature>() + .discriminateOn("cipher") + .alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature()) + .build("BlindedDenominationSignature"); + +export const codecForExchangeWithdrawBatchResponse = + (): Codec<ExchangeWithdrawBatchResponse> => + buildCodecForObject<ExchangeWithdrawBatchResponse>() + .property("ev_sigs", codecForList(codecForWithdrawResponse())) + .build("WithdrawBatchResponse"); + +export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> => + buildCodecForObject<ExchangeMeltResponse>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("noreveal_index", codecForNumber()) + .property("refresh_base_url", codecOptional(codecForString())) + .build("ExchangeMeltResponse"); + +export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> => + buildCodecForObject<ExchangeRevealItem>() + .property("ev_sig", codecForBlindedDenominationSignature()) + .build("ExchangeRevealItem"); + +export const codecForExchangeRevealResponse = + (): Codec<ExchangeRevealResponse> => + buildCodecForObject<ExchangeRevealResponse>() + .property("ev_sigs", codecForList(codecForExchangeRevealItem())) + .build("ExchangeRevealResponse"); + +export interface FutureKeysResponse { + future_denoms: any[]; + + future_signkeys: any[]; + + master_pub: string; + + denom_secmod_public_key: string; + + // Public key of the signkey security module. + signkey_secmod_public_key: string; +} + +export const codecForKeysManagementResponse = (): Codec<FutureKeysResponse> => + buildCodecForObject<FutureKeysResponse>() + .property("master_pub", codecForString()) + .property("future_signkeys", codecForList(codecForAny())) + .property("future_denoms", codecForList(codecForAny())) + .property("denom_secmod_public_key", codecForAny()) + .property("signkey_secmod_public_key", codecForAny()) + .build("FutureKeysResponse"); + +export interface PurseDeposit { + /** + * Amount to be deposited, can be a fraction of the + * coin's total value. + */ + amount: AmountString; + + /** + * Hash of denomination RSA key with which the coin is signed. + */ + denom_pub_hash: HashCodeString; + + /** + * Exchange's unblinded RSA signature of the coin. + */ + ub_sig: UnblindedSignature; + + /** + * Age commitment for the coin, if the denomination is age-restricted. + */ + age_commitment?: string[]; + + /** + * Attestation for the minimum age, if the denomination is age-restricted. + */ + attest?: string; + + /** + * Signature over TALER_PurseDepositSignaturePS + * of purpose TALER_SIGNATURE_WALLET_PURSE_DEPOSIT + * made by the customer with the + * coin's private key. + */ + coin_sig: EddsaSignatureString; + + /** + * Public key of the coin being deposited into the purse. + */ + coin_pub: EddsaPublicKeyString; +} + +export interface ExchangePurseMergeRequest { + // payto://-URI of the account the purse is to be merged into. + // Must be of the form: 'payto://taler/$EXCHANGE_URL/$RESERVE_PUB'. + payto_uri: string; + + // EdDSA signature of the account/reserve affirming the merge + // over a TALER_AccountMergeSignaturePS. + // Must be of purpose TALER_SIGNATURE_ACCOUNT_MERGE + reserve_sig: EddsaSignatureString; + + // EdDSA signature of the purse private key affirming the merge + // over a TALER_PurseMergeSignaturePS. + // Must be of purpose TALER_SIGNATURE_PURSE_MERGE. + merge_sig: EddsaSignatureString; + + // Client-side timestamp of when the merge request was made. + merge_timestamp: TalerProtocolTimestamp; +} + +export interface ExchangeGetContractResponse { + purse_pub: string; + econtract_sig: string; + econtract: string; +} + +export const codecForExchangeGetContractResponse = + (): Codec<ExchangeGetContractResponse> => + buildCodecForObject<ExchangeGetContractResponse>() + .property("purse_pub", codecForString()) + .property("econtract_sig", codecForString()) + .property("econtract", codecForString()) + .build("ExchangeGetContractResponse"); + +/** + * Contract terms between two wallets (as opposed to a merchant and wallet). + */ +export interface PeerContractTerms { + amount: AmountString; + summary: string; + purse_expiration: TalerProtocolTimestamp; +} + +export interface EncryptedContract { + // Encrypted contract. + econtract: string; + + // Signature over the (encrypted) contract. + econtract_sig: string; + + // Ephemeral public key for the DH operation to decrypt the encrypted contract. + contract_pub: string; +} + +/** + * Payload for /reserves/{reserve_pub}/purse + * endpoint of the exchange. + */ +export interface ExchangeReservePurseRequest { + /** + * Minimum amount that must be credited to the reserve, that is + * the total value of the purse minus the deposit fees. + * If the deposit fees are lower, the contribution to the + * reserve can be higher! + */ + purse_value: AmountString; + + // Minimum age required for all coins deposited into the purse. + min_age: number; + + // Purse fee the reserve owner is willing to pay + // for the purse creation. Optional, if not present + // the purse is to be created from the purse quota + // of the reserve. + purse_fee: AmountString; + + // Optional encrypted contract, in case the buyer is + // proposing the contract and thus establishing the + // purse with the payment. + econtract?: EncryptedContract; + + // EdDSA public key used to approve merges of this purse. + merge_pub: EddsaPublicKeyString; + + // EdDSA signature of the purse private key affirming the merge + // over a TALER_PurseMergeSignaturePS. + // Must be of purpose TALER_SIGNATURE_PURSE_MERGE. + merge_sig: EddsaSignatureString; + + // EdDSA signature of the account/reserve affirming the merge. + // Must be of purpose TALER_SIGNATURE_WALLET_ACCOUNT_MERGE + reserve_sig: EddsaSignatureString; + + // Purse public key. + purse_pub: EddsaPublicKeyString; + + // EdDSA signature of the purse over + // TALER_PurseRequestSignaturePS of + // purpose TALER_SIGNATURE_PURSE_REQUEST + // confirming that the + // above details hold for this purse. + purse_sig: EddsaSignatureString; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCodeString; + + // Client-side timestamp of when the merge request was made. + merge_timestamp: TalerProtocolTimestamp; + + // Indicative time by which the purse should expire + // if it has not been paid. + purse_expiration: TalerProtocolTimestamp; +} + +export interface ExchangePurseDeposits { + // Array of coins to deposit into the purse. + deposits: PurseDeposit[]; +} + +/** + * @deprecated batch deposit should be used. + */ +export interface ExchangeDepositRequest { + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: AmountString; + + // The merchant's account details. + // In case of an auction policy, it refers to the seller. + merchant_payto_uri: string; + + // The salt is used to hide the payto_uri from customers + // when computing the h_wire of the merchant. + wire_salt: string; + + // SHA-512 hash of the contract of the merchant with the customer. Further + // details are never disclosed to the exchange. + h_contract_terms: HashCodeString; + + // Hash of denomination RSA key with which the coin is signed. + denom_pub_hash: HashCodeString; + + // Exchange's unblinded RSA signature of the coin. + ub_sig: UnblindedSignature; + + // Timestamp when the contract was finalized. + timestamp: TalerProtocolTimestamp; + + // Indicative time by which the exchange undertakes to transfer the funds to + // the merchant, in case of successful payment. A wire transfer deadline of 'never' + // is not allowed. + wire_transfer_deadline: TalerProtocolTimestamp; + + // EdDSA public key of the merchant, so that the client can identify the + // merchant for refund requests. + // + // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a + // policy via extension. + merchant_pub: EddsaPublicKeyString; + + // Date until which the merchant can issue a refund to the customer via the + // exchange, to be omitted if refunds are not allowed. + // + // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a + // policy via extension. + refund_deadline?: TalerProtocolTimestamp; + + // CAVEAT: THIS IS WORK IN PROGRESS + // (Optional) policy for the deposit. + // This might be a refund, auction or escrow policy. + // + // Note that support for policies is an optional feature of the exchange. + // Optional features are so called "extensions" in Taler. The exchange + // provides the list of supported extensions, including policies, in the + // ExtensionsManifestsResponse response to the /keys endpoint. + policy?: any; + + // Signature over TALER_DepositRequestPS, made by the customer with the + // coin's private key. + coin_sig: EddsaSignatureString; + + h_age_commitment?: string; +} + +export type TrackTransaction = + | ({ type: "accepted" } & TrackTransactionAccepted) + | ({ type: "wired" } & TrackTransactionWired); + +export interface BatchDepositSuccess { + // Optional base URL of the exchange for looking up wire transfers + // associated with this transaction. If not given, + // the base URL is the same as the one used for this request. + // Can be used if the base URL for ``/transactions/`` differs from that + // for ``/coins/``, i.e. for load balancing. Clients SHOULD + // respect the ``transaction_base_url`` if provided. Any HTTP server + // belonging to an exchange MUST generate a 307 or 308 redirection + // to the correct base URL should a client uses the wrong base + // URL, or if the base URL has changed since the deposit. + transaction_base_url?: string; + + // Timestamp when the deposit was received by the exchange. + exchange_timestamp: TalerProtocolTimestamp; + + // `Public EdDSA key of the exchange <sign-key-pub>` that was used to + // generate the signature. + // Should match one of the exchange's signing keys from ``/keys``. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; + + // Array of deposit confirmation signatures from the exchange + // Entries must be in the same order the coins were given + // in the batch deposit request. + exchange_sig: EddsaSignatureString; +} + +export const codecForBatchDepositSuccess = (): Codec<BatchDepositSuccess> => + buildCodecForObject<BatchDepositSuccess>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("exchange_timestamp", codecForTimestamp) + .property("transaction_base_url", codecOptional(codecForString())) + .build("BatchDepositSuccess"); + +export interface TrackTransactionWired { + // Raw wire transfer identifier of the deposit. + wtid: Base32String; + + // When was the wire transfer given to the bank. + execution_time: TalerProtocolTimestamp; + + // The contribution of this coin to the total (without fees) + coin_contribution: AmountString; + + // Binary-only Signature_ with purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE + // over a TALER_ConfirmWirePS + // whereby the exchange affirms the successful wire transfer. + exchange_sig: EddsaSignatureString; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. Again given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; +} + +export const codecForTackTransactionWired = (): Codec<TrackTransactionWired> => + buildCodecForObject<TrackTransactionWired>() + .property("wtid", codecForString()) + .property("execution_time", codecForTimestamp) + .property("coin_contribution", codecForAmountString()) + .property("exchange_sig", codecForString()) + .property("exchange_pub", codecForString()) + .build("TackTransactionWired"); + +export interface TrackTransactionAccepted { + // Legitimization target that the merchant should + // use to check for its KYC status using + // the /kyc-check/$REQUIREMENT_ROW/... endpoint. + // Optional, not present if the deposit has not + // yet been aggregated to the point that a KYC + // need has been evaluated. + requirement_row?: number; + + // True if the KYC check for the merchant has been + // satisfied. False does not mean that KYC + // is strictly needed, unless also a + // legitimization_uuid is provided. + kyc_ok: boolean; + + // Time by which the exchange currently thinks the deposit will be executed. + // Actual execution may be later if the KYC check is not satisfied by then. + execution_time: TalerProtocolTimestamp; +} + +export const codecForTackTransactionAccepted = + (): Codec<TrackTransactionAccepted> => + buildCodecForObject<TrackTransactionAccepted>() + .property("requirement_row", codecOptional(codecForNumber())) + .property("kyc_ok", codecForBoolean()) + .property("execution_time", codecForTimestamp) + .build("TackTransactionAccepted"); + +export const codecForPeerContractTerms = (): Codec<PeerContractTerms> => + buildCodecForObject<PeerContractTerms>() + .property("summary", codecForString()) + .property("amount", codecForAmountString()) + .property("purse_expiration", codecForTimestamp) + .build("PeerContractTerms"); + +export interface ExchangeBatchDepositRequest { + // The merchant's account details. + merchant_payto_uri: string; + + // The salt is used to hide the ``payto_uri`` from customers + // when computing the ``h_wire`` of the merchant. + wire_salt: WireSalt; + + // SHA-512 hash of the contract of the merchant with the customer. Further + // details are never disclosed to the exchange. + h_contract_terms: HashCodeString; + + // The list of coins that are going to be deposited with this Request. + coins: BatchDepositRequestCoin[]; + + // Timestamp when the contract was finalized. + timestamp: TalerProtocolTimestamp; + + // Indicative time by which the exchange undertakes to transfer the funds to + // the merchant, in case of successful payment. A wire transfer deadline of 'never' + // is not allowed. + wire_transfer_deadline: TalerProtocolTimestamp; + + // EdDSA `public key of the merchant <merchant-pub>`, so that the client can identify the + // merchant for refund requests. + merchant_pub: EddsaPublicKeyString; + + // Date until which the merchant can issue a refund to the customer via the + // exchange, to be omitted if refunds are not allowed. + // + // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a + // policy via extension. + refund_deadline?: TalerProtocolTimestamp; + + // CAVEAT: THIS IS WORK IN PROGRESS + // (Optional) policy for the batch-deposit. + // This might be a refund, auction or escrow policy. + policy?: any; +} + +export interface BatchDepositRequestCoin { + // EdDSA public key of the coin being deposited. + coin_pub: EddsaPublicKeyString; + + // Hash of denomination RSA key with which the coin is signed. + denom_pub_hash: HashCodeString; + + // Exchange's unblinded RSA signature of the coin. + ub_sig: UnblindedSignature; + + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: AmountString; + + // Signature over `TALER_DepositRequestPS`, made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignatureString; + + h_age_commitment?: string; +} diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -0,0 +1,704 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { codecForAmountString } from "./amounts.js"; +import { + Codec, + buildCodecForObject, + codecForAny, + codecForBoolean, + codecForList, + codecForNumber, + codecForString, + codecOptional, +} from "./codec.js"; +import { codecForProduct } from "./index.js"; +import { + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForDuration, + codecForTimestamp, +} from "./time.js"; +import { + AmountString, + EddsaPublicKeyString, + EddsaSignatureString, + ImageDataUrl, + Integer, + InternationalizedString, + codecForInternationalizedString, +} from "./types-taler-common.js"; + +/** + * Proposal returned from the contract URL. + */ +export interface Proposal { + /** + * Contract terms for the propoal. + * Raw, un-decoded JSON object. + */ + contract_terms: any; + + /** + * Signature over contract, made by the merchant. The public key used for signing + * must be contract_terms.merchant_pub. + */ + sig: string; +} + +export interface MerchantPayResponse { + sig: string; + pos_confirmation?: string; +} + +interface MerchantOrderStatusPaid { + // Was the payment refunded (even partially, via refund or abort)? + refunded: boolean; + + // Is any amount of the refund still waiting to be picked up (even partially)? + refund_pending: boolean; + + // Amount that was refunded in total. + refund_amount: AmountString; + + // Amount that already taken by the wallet. + refund_taken: AmountString; +} + +interface MerchantOrderRefundResponse { + /** + * Amount that was refunded in total. + */ + refund_amount: AmountString; + + /** + * Successful refunds for this payment, empty array for none. + */ + refunds: MerchantCoinRefundStatus[]; + + /** + * Public key of the merchant. + */ + merchant_pub: EddsaPublicKeyString; +} + +/** + * Response from the internal merchant API. + */ +export class CheckPaymentResponse { + order_status: string; + refunded: boolean | undefined; + refunded_amount: string | undefined; + contract_terms: any | undefined; + taler_pay_uri: string | undefined; + contract_url: string | undefined; +} + +export const codecForMerchantRefundPermission = + (): Codec<MerchantAbortPayRefundDetails> => + buildCodecForObject<MerchantAbortPayRefundDetails>() + .property("refund_amount", codecForAmountString()) + .property("refund_fee", codecForAmountString()) + .property("coin_pub", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("exchange_sig", codecOptional(codecForString())) + .property("exchange_pub", codecOptional(codecForString())) + .build("MerchantRefundPermission"); + +export const codecForProposal = (): Codec<Proposal> => + buildCodecForObject<Proposal>() + .property("contract_terms", codecForAny()) + .property("sig", codecForString()) + .build("Proposal"); + +export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> => + buildCodecForObject<CheckPaymentResponse>() + .property("order_status", codecForString()) + .property("refunded", codecOptional(codecForBoolean())) + .property("refunded_amount", codecOptional(codecForString())) + .property("contract_terms", codecOptional(codecForAny())) + .property("taler_pay_uri", codecOptional(codecForString())) + .property("contract_url", codecOptional(codecForString())) + .build("CheckPaymentResponse"); + +export type MerchantCoinRefundStatus = + | MerchantCoinRefundSuccessStatus + | MerchantCoinRefundFailureStatus; + +export interface MerchantCoinRefundSuccessStatus { + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: EddsaSignatureString; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: TalerProtocolTimestamp; +} + +export interface MerchantCoinRefundFailureStatus { + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: any; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: TalerProtocolTimestamp; +} + +export interface MerchantOrderStatusUnpaid { + /** + * URI that the wallet must process to complete the payment. + */ + taler_pay_uri: string; + + /** + * Alternative order ID which was paid for already in the same session. + * + * Only given if the same product was purchased before in the same session. + */ + already_paid_order_id?: string; +} + +export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => + buildCodecForObject<MerchantPayResponse>() + .property("sig", codecForString()) + .property("pos_confirmation", codecOptional(codecForString())) + .build("MerchantPayResponse"); + +export const codecForMerchantOrderStatusPaid = + (): Codec<MerchantOrderStatusPaid> => + buildCodecForObject<MerchantOrderStatusPaid>() + .property("refund_amount", codecForAmountString()) + .property("refund_taken", codecForAmountString()) + .property("refund_pending", codecForBoolean()) + .property("refunded", codecForBoolean()) + .build("MerchantOrderStatusPaid"); + +export const codecForMerchantOrderStatusUnpaid = + (): Codec<MerchantOrderStatusUnpaid> => + buildCodecForObject<MerchantOrderStatusUnpaid>() + .property("taler_pay_uri", codecForString()) + .property("already_paid_order_id", codecOptional(codecForString())) + .build("MerchantOrderStatusUnpaid"); + +export interface AbortRequest { + // hash of the order's contract terms (this is used to authenticate the + // wallet/customer in case $ORDER_ID is guessable). + h_contract: string; + + // List of coins the wallet would like to see refunds for. + // (Should be limited to the coins for which the original + // payment succeeded, as far as the wallet knows.) + coins: AbortingCoin[]; +} + +export interface AbortingCoin { + // Public key of a coin for which the wallet is requesting an abort-related refund. + coin_pub: EddsaPublicKeyString; + + // The amount to be refunded (matches the original contribution) + contribution: AmountString; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; +} + +export interface AbortResponse { + // List of refund responses about the coins that the wallet + // requested an abort for. In the same order as the 'coins' + // from the original request. + // The rtransaction_id is implied to be 0. + refunds: MerchantAbortPayRefundStatus[]; +} + +export type MerchantAbortPayRefundStatus = + | MerchantAbortPayRefundSuccessStatus + | MerchantAbortPayRefundFailureStatus; + +// Details about why a refund failed. +export interface MerchantAbortPayRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: unknown; +} + +// Additional details needed to verify the refund confirmation signature +// (h_contract_terms and merchant_pub) are already known +// to the wallet and thus not included. +export interface MerchantAbortPayRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: string; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: string; +} + +/** + * Information about an exchange as stored inside a + * merchant's contract terms. + */ +export interface ExchangeHandle { + // The exchange's base URL. + url: string; + + // Master public key of the exchange. + master_pub: EddsaPublicKeyString; +} + +export interface AuditorHandle { + /** + * Official name of the auditor. + */ + name: string; + + /** + * Master public signing key of the auditor. + */ + auditor_pub: EddsaPublicKeyString; + + /** + * Base URL of the auditor. + */ + url: string; +} + +// Delivery location, loosely modeled as a subset of +// ISO20022's PostalAddress25. +export interface Location { + // Nation with its own government. + country?: string; + + // Identifies a subdivision of a country such as state, region, county. + country_subdivision?: string; + + // Identifies a subdivision within a country sub-division. + district?: string; + + // Name of a built-up area, with defined boundaries, and a local government. + town?: string; + + // Specific location name within the town. + town_location?: string; + + // Identifier consisting of a group of letters and/or numbers that + // is added to a postal address to assist the sorting of mail. + post_code?: string; + + // Name of a street or thoroughfare. + street?: string; + + // Name of the building or house. + building_name?: string; + + // Number that identifies the position of a building on a street. + building_number?: string; + + // Free-form address lines, should not exceed 7 elements. + address_lines?: string[]; +} + +export interface MerchantInfo { + // The merchant's legal name of business. + name: string; + + // Label for a location with the business address of the merchant. + email?: string; + + // Label for a location with the business address of the merchant. + website?: string; + + // An optional base64-encoded product image. + logo?: ImageDataUrl; + + // Label for a location with the business address of the merchant. + address?: Location; + + // Label for a location that denotes the jurisdiction for disputes. + // Some of the typical fields for a location (such as a street address) may be absent. + jurisdiction?: Location; +} + +export interface Tax { + // the name of the tax + name: string; + + // amount paid in tax + tax: AmountString; +} + +export interface Product { + // merchant-internal identifier for the product. + product_id?: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions + description_i18n?: InternationalizedString; + + // The number of units of the product to deliver to the customer. + quantity?: Integer; + + // The unit in which the product is measured (liters, kilograms, packages, etc.) + unit?: string; + + // The price of the product; this is the total price for quantity times unit of this product. + price?: AmountString; + + // An optional base64-encoded product image + image?: ImageDataUrl; + + // a list of taxes paid by the merchant for this product. Can be empty. + taxes?: Tax[]; + + // time indicating when this product should be delivered + delivery_date?: TalerProtocolTimestamp; +} + +/** + * Contract terms from a merchant. + * FIXME: Add type field! + */ +export interface MerchantContractTerms { + // The hash of the merchant instance's wire details. + h_wire: string; + + // Specifies for how long the wallet should try to get an + // automatic refund for the purchase. If this field is + // present, the wallet should wait for a few seconds after + // the purchase and then automatically attempt to obtain + // a refund. The wallet should probe until "delay" + // after the payment was successful (i.e. via long polling + // or via explicit requests with exponential back-off). + // + // In particular, if the wallet is offline + // at that time, it MUST repeat the request until it gets + // one response from the merchant after the delay has expired. + // If the refund is granted, the wallet MUST automatically + // recover the payment. This is used in case a merchant + // knows that it might be unable to satisfy the contract and + // desires for the wallet to attempt to get the refund without any + // customer interaction. Note that it is NOT an error if the + // merchant does not grant a refund. + auto_refund?: TalerProtocolDuration; + + // Wire transfer method identifier for the wire method associated with h_wire. + // The wallet may only select exchanges via a matching auditor if the + // exchange also supports this wire method. + // The wire transfer fees must be added based on this wire transfer method. + wire_method: string; + + // Human-readable description of the whole purchase. + summary: string; + + // Map from IETF BCP 47 language tags to localized summaries. + summary_i18n?: InternationalizedString; + + // Unique, free-form identifier for the proposal. + // Must be unique within a merchant instance. + // For merchants that do not store proposals in their DB + // before the customer paid for them, the order_id can be used + // by the frontend to restore a proposal from the information + // encoded in it (such as a short product identifier and timestamp). + order_id: string; + + // Total price for the transaction. + // The exchange will subtract deposit fees from that amount + // before transferring it to the merchant. + amount: string; + + // Nonce generated by the wallet and echoed by the merchant + // in this field when the proposal is generated. + nonce: string; + + // After this deadline, the merchant won't accept payments for the contract. + pay_deadline: TalerProtocolTimestamp; + + // More info about the merchant, see below. + merchant: MerchantInfo; + + // Merchant's public key used to sign this proposal; this information + // is typically added by the backend. Note that this can be an ephemeral key. + merchant_pub: string; + + // Time indicating when the order should be delivered. + // May be overwritten by individual products. + delivery_date?: TalerProtocolTimestamp; + + // Delivery location for (all!) products. + delivery_location?: Location; + + // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + exchanges: ExchangeHandle[]; + + // List of products that are part of the purchase (see Product). + products?: Product[]; + + // After this deadline has passed, no refunds will be accepted. + refund_deadline: TalerProtocolTimestamp; + + // Transfer deadline for the exchange. Must be in the + // deposit permissions of coins used to pay for this order. + wire_transfer_deadline: TalerProtocolTimestamp; + + // Time when this contract was generated. + timestamp: TalerProtocolTimestamp; + + // Base URL of the (public!) merchant backend API. + // Must be an absolute URL that ends with a slash. + merchant_base_url: string; + + // URL that will show that the order was successful after + // it has been paid for. Optional, but either fulfillment_url + // or fulfillment_message must be specified in every + // contract terms. + // + // If a non-unique fulfillment URL is used, a customer can only + // buy the order once and will be redirected to a previous purchase + // when trying to buy an order with the same fulfillment URL a second + // time. This is useful for digital goods that a customer only needs + // to buy once but should be able to repeatedly download. + // + // For orders where the customer is expected to be able to make + // repeated purchases (for equivalent goods), the fulfillment URL + // should be made unique for every order. The easiest way to do + // this is to include a unique order ID in the fulfillment URL. + // + // When POSTing to the merchant, the placeholder text "${ORDER_ID}" + // is be replaced with the actual order ID (useful if the + // order ID is generated server-side and needs to be + // in the URL). Note that this placeholder can only be used once. + // Front-ends may use other means to generate a unique fulfillment URL. + fulfillment_url?: string; + + // URL where the same contract could be ordered again (if + // available). Returned also at the public order endpoint + // for people other than the actual buyer (hence public, + // in case order IDs are guessable). + public_reorder_url?: string; + + // Message shown to the customer after paying for the order. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_message?: string; + + // Map from IETF BCP 47 language tags to localized fulfillment + // messages. + fulfillment_message_i18n?: InternationalizedString; + + // Maximum total deposit fee accepted by the merchant for this contract. + // Overrides defaults of the merchant instance. + max_fee: string; + + // Extra data that is only interpreted by the merchant frontend. + // Useful when the merchant needs to store extra information on a + // contract without storing it separately in their database. + // Must really be an Object (not a string, integer, float or array). + extra?: any; + + // Minimum age the buyer must have (in years). Default is 0. + // This value is at least as large as the maximum over all + // minimum age requirements of the products in this contract. + // It might also be set independent of any product, due to + // legal requirements. + minimum_age?: Integer; +} + +/** + * Refund permission in the format that the merchant gives it to us. + */ +export interface MerchantAbortPayRefundDetails { + /** + * Amount to be refunded. + */ + refund_amount: string; + + /** + * Fee for the refund. + */ + refund_fee: string; + + /** + * Public key of the coin being refunded. + */ + coin_pub: string; + + /** + * Refund transaction ID between merchant and exchange. + */ + rtransaction_id: number; + + /** + * Exchange's key used for the signature. + */ + exchange_pub?: string; + + /** + * Exchange's signature to confirm the refund. + */ + exchange_sig?: string; + + /** + * Error replay from the exchange (if any). + */ + exchange_reply?: any; + + /** + * Error code from the exchange (if any). + */ + exchange_code?: number; + + /** + * HTTP status code of the exchange's response + * to the merchant's refund request. + */ + exchange_http_status: number; +} + +/** + * Reserve signature, defined as separate class to facilitate + * schema validation. + */ +export interface MerchantBlindSigWrapperV1 { + /** + * Reserve signature. + */ + blind_sig: string; +} + +export const codecForExchangeHandle = (): Codec<ExchangeHandle> => + buildCodecForObject<ExchangeHandle>() + .property("master_pub", codecForString()) + .property("url", codecForString()) + .build("ExchangeHandle"); + +export const codecForAuditorHandle = (): Codec<AuditorHandle> => + buildCodecForObject<AuditorHandle>() + .property("name", codecForString()) + .property("auditor_pub", codecForString()) + .property("url", codecForString()) + .build("AuditorHandle"); + +export const codecForLocation = (): Codec<Location> => + buildCodecForObject<Location>() + .property("country", codecOptional(codecForString())) + .property("country_subdivision", codecOptional(codecForString())) + .property("building_name", codecOptional(codecForString())) + .property("building_number", codecOptional(codecForString())) + .property("district", codecOptional(codecForString())) + .property("street", codecOptional(codecForString())) + .property("post_code", codecOptional(codecForString())) + .property("town", codecOptional(codecForString())) + .property("town_location", codecOptional(codecForString())) + .property("address_lines", codecOptional(codecForList(codecForString()))) + .build("Location"); + +export const codecForMerchantInfo = (): Codec<MerchantInfo> => + buildCodecForObject<MerchantInfo>() + .property("name", codecForString()) + .property("address", codecOptional(codecForLocation())) + .property("jurisdiction", codecOptional(codecForLocation())) + .build("MerchantInfo"); + +export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> => + buildCodecForObject<MerchantContractTerms>() + .property("order_id", codecForString()) + .property("fulfillment_url", codecOptional(codecForString())) + .property("fulfillment_message", codecOptional(codecForString())) + .property( + "fulfillment_message_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("merchant_base_url", codecForString()) + .property("h_wire", codecForString()) + .property("auto_refund", codecOptional(codecForDuration)) + .property("wire_method", codecForString()) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .property("nonce", codecForString()) + .property("amount", codecForAmountString()) + .property("pay_deadline", codecForTimestamp) + .property("refund_deadline", codecForTimestamp) + .property("wire_transfer_deadline", codecForTimestamp) + .property("timestamp", codecForTimestamp) + .property("delivery_location", codecOptional(codecForLocation())) + .property("delivery_date", codecOptional(codecForTimestamp)) + .property("max_fee", codecForAmountString()) + .property("merchant", codecForMerchantInfo()) + .property("merchant_pub", codecForString()) + .property("exchanges", codecForList(codecForExchangeHandle())) + .property("products", codecOptional(codecForList(codecForProduct()))) + .property("extra", codecForAny()) + .property("minimum_age", codecOptional(codecForNumber())) + .build("MerchantContractTerms"); diff --git a/packages/taler-util/src/types-taler-sync.ts b/packages/taler-util/src/types-taler-sync.ts @@ -0,0 +1,44 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { AmountString } from "./types-taler-common.js"; + +export interface BackupRecovery { + walletRootPriv: string; + providers: { + name: string; + url: string; + }[]; +} + +export class BackupBackupProviderTerms { + /** + * Last known supported protocol version. + */ + supported_protocol_version: string; + + /** + * Last known annual fee. + */ + annual_fee: AmountString; + + /** + * Last known storage limit. + */ + storage_limit_in_megabytes: number; +} diff --git a/packages/taler-util/src/types-taler-wallet-transactions.ts b/packages/taler-util/src/types-taler-wallet-transactions.ts @@ -0,0 +1,804 @@ +/* + This file is part of GNU Taler + (C) 2019-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Type and schema definitions for the wallet's transaction list. + * + * @author Florian Dold + * @author Torsten Grote + */ + +/** + * Imports. + */ +import { + Codec, + buildCodecForObject, + codecForAny, + codecForBoolean, + codecForConstString, + codecForEither, + codecForList, + codecForString, + codecOptional, +} from "./codec.js"; +import { TalerPreciseTimestamp, TalerProtocolTimestamp } from "./time.js"; +import { + AmountString, + InternationalizedString, + codecForInternationalizedString, +} from "./types-taler-common.js"; +import { MerchantInfo, codecForMerchantInfo } from "./types-taler-merchant.js"; +import { + RefreshReason, + ScopeInfo, + TalerErrorDetail, + TransactionIdStr, + TransactionStateFilter, + WithdrawalExchangeAccountDetails, + codecForScopeInfo, +} from "./types-taler-wallet.js"; + +export interface TransactionsRequest { + /** + * return only transactions in the given currency + * + * it will be removed in next release + * + * @deprecated use scopeInfo + */ + currency?: string; + + /** + * return only transactions in the given scopeInfo + */ + scopeInfo?: ScopeInfo; + + /** + * if present, results will be limited to transactions related to the given search string + */ + search?: string; + + /** + * Sort order of the transaction items. + * By default, items are sorted ascending by their + * main timestamp. + * + * ascending: ascending by timestamp, but pending transactions first + * descending: ascending by timestamp, but pending transactions first + * stable-ascending: ascending by timestamp, with pending transactions amidst other transactions + * (stable in the sense of: pending transactions don't jump around) + */ + sort?: "ascending" | "descending" | "stable-ascending"; + + /** + * If true, include all refreshes in the transactions list. + */ + includeRefreshes?: boolean; + + filterByState?: TransactionStateFilter; +} + +export interface TransactionState { + major: TransactionMajorState; + minor?: TransactionMinorState; +} + +export enum TransactionMajorState { + // No state, only used when reporting transitions into the initial state + None = "none", + Pending = "pending", + Done = "done", + Aborting = "aborting", + Aborted = "aborted", + Dialog = "dialog", + Finalizing = "finalizing", + // Plain suspended is always a suspended pending state. + Suspended = "suspended", + SuspendedFinalizing = "suspended-finalizing", + SuspendedAborting = "suspended-aborting", + Failed = "failed", + Expired = "expired", + // Only used for the notification, never in the transaction history + Deleted = "deleted", +} + +export enum TransactionMinorState { + // Placeholder until D37 is fully implemented + Unknown = "unknown", + Deposit = "deposit", + KycRequired = "kyc", + AmlRequired = "aml", + MergeKycRequired = "merge-kyc", + Track = "track", + SubmitPayment = "submit-payment", + RebindSession = "rebind-session", + Refresh = "refresh", + Pickup = "pickup", + AutoRefund = "auto-refund", + User = "user", + Bank = "bank", + Exchange = "exchange", + ClaimProposal = "claim-proposal", + CheckRefund = "check-refund", + CreatePurse = "create-purse", + DeletePurse = "delete-purse", + RefreshExpired = "refresh-expired", + Ready = "ready", + Merge = "merge", + Repurchase = "repurchase", + BankRegisterReserve = "bank-register-reserve", + BankConfirmTransfer = "bank-confirm-transfer", + WithdrawCoins = "withdraw-coins", + ExchangeWaitReserve = "exchange-wait-reserve", + AbortingBank = "aborting-bank", + Aborting = "aborting", + Refused = "refused", + Withdraw = "withdraw", + MerchantOrderProposed = "merchant-order-proposed", + Proposed = "proposed", + RefundAvailable = "refund-available", + AcceptRefund = "accept-refund", + PaidByOther = "paid-by-other", + CompletedByOtherWallet = "completed-by-other-wallet", +} + +export enum TransactionAction { + Delete = "delete", + Suspend = "suspend", + Resume = "resume", + Abort = "abort", + Fail = "fail", + Retry = "retry", +} + +export interface TransactionsResponse { + // a list of past and pending transactions sorted by pending, timestamp and transactionId. + // In case two events are both pending and have the same timestamp, + // they are sorted by the transactionId + // (lexically ascending and locale-independent comparison). + transactions: Transaction[]; +} + +export interface TransactionCommon { + // opaque unique ID for the transaction, used as a starting point for paginating queries + // and for invoking actions on the transaction (e.g. deleting/hiding it from the history) + transactionId: TransactionIdStr; + + // the type of the transaction; different types might provide additional information + type: TransactionType; + + // main timestamp of the transaction + timestamp: TalerPreciseTimestamp; + + /** + * Transaction state, as per DD37. + */ + txState: TransactionState; + + /** + * Possible transitions based on the current state. + */ + txActions: TransactionAction[]; + + /** + * Raw amount of the transaction (exclusive of fees or other extra costs). + */ + amountRaw: AmountString; + + /** + * Amount added or removed from the wallet's balance (including all fees and other costs). + */ + amountEffective: AmountString; + + error?: TalerErrorDetail; + + /** + * If the transaction minor state is in KycRequired this field is going to + * have the location where the user need to go to complete KYC information. + */ + kycUrl?: string; +} + +export type Transaction = + | TransactionWithdrawal + | TransactionPayment + | TransactionRefund + | TransactionRefresh + | TransactionDeposit + | TransactionPeerPullCredit + | TransactionPeerPullDebit + | TransactionPeerPushCredit + | TransactionPeerPushDebit + | TransactionInternalWithdrawal + | TransactionRecoup + | TransactionDenomLoss; + +export enum TransactionType { + Withdrawal = "withdrawal", + InternalWithdrawal = "internal-withdrawal", + Payment = "payment", + Refund = "refund", + Refresh = "refresh", + Deposit = "deposit", + PeerPushDebit = "peer-push-debit", + PeerPushCredit = "peer-push-credit", + PeerPullDebit = "peer-pull-debit", + PeerPullCredit = "peer-pull-credit", + Recoup = "recoup", + DenomLoss = "denom-loss", +} + +export enum WithdrawalType { + TalerBankIntegrationApi = "taler-bank-integration-api", + ManualTransfer = "manual-transfer", +} + +export type WithdrawalDetails = + | WithdrawalDetailsForManualTransfer + | WithdrawalDetailsForTalerBankIntegrationApi; + +interface WithdrawalDetailsForManualTransfer { + type: WithdrawalType.ManualTransfer; + + /** + * Payto URIs that the exchange supports. + * + * Already contains the amount and message. + * + * @deprecated in favor of exchangeCreditAccounts + */ + exchangePaytoUris: string[]; + + exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[]; + + // Public key of the reserve + reservePub: string; + + /** + * Is the reserve ready for withdrawal? + */ + reserveIsReady: boolean; +} + +interface WithdrawalDetailsForTalerBankIntegrationApi { + type: WithdrawalType.TalerBankIntegrationApi; + + /** + * Set to true if the bank has confirmed the withdrawal, false if not. + * An unconfirmed withdrawal usually requires user-input and should be highlighted in the UI. + * See also bankConfirmationUrl below. + */ + confirmed: boolean; + + /** + * If the withdrawal is unconfirmed, this can include a URL for user + * initiated confirmation. + */ + bankConfirmationUrl?: string; + + // Public key of the reserve + reservePub: string; + + /** + * Is the reserve ready for withdrawal? + */ + reserveIsReady: boolean; + + /** + * Is the bank transfer for the withdrawal externally confirmed? + */ + externalConfirmation?: boolean; + + exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[]; +} + +export enum DenomLossEventType { + DenomExpired = "denom-expired", + DenomVanished = "denom-vanished", + DenomUnoffered = "denom-unoffered", +} + +/** + * A transaction to indicate financial loss due to denominations + * that became unusable for deposits. + */ +export interface TransactionDenomLoss extends TransactionCommon { + type: TransactionType.DenomLoss; + lossEventType: DenomLossEventType; + exchangeBaseUrl: string; +} + +/** + * A withdrawal transaction (either bank-integrated or manual). + */ +export interface TransactionWithdrawal extends TransactionCommon { + type: TransactionType.Withdrawal; + + /** + * Exchange of the withdrawal. + */ + exchangeBaseUrl: string | undefined; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + withdrawalDetails: WithdrawalDetails; +} + +/** + * Internal withdrawal operation, only reported on request. + * + * Some transactions (peer-*-credit) internally do a withdrawal, + * but only the peer-*-credit transaction is reported. + * + * The internal withdrawal transaction allows to access the details of + * the underlying withdrawal for testing/debugging. + * + * It is usually not reported, so that amounts of transactions properly + * add up, since the amountEffecive of the withdrawal is already reported + * in the peer-*-credit transaction. + */ +export interface TransactionInternalWithdrawal extends TransactionCommon { + type: TransactionType.InternalWithdrawal; + + /** + * Exchange of the withdrawal. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + withdrawalDetails: WithdrawalDetails; +} + +export interface PeerInfoShort { + expiration: TalerProtocolTimestamp | undefined; + summary: string | undefined; +} + +/** + * Credit because we were paid for a P2P invoice we created. + */ +export interface TransactionPeerPullCredit extends TransactionCommon { + type: TransactionType.PeerPullCredit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + /** + * URI to send to the other party. + * + * Only available in the right state. + */ + talerUri: string | undefined; +} + +/** + * Debit because we paid someone's invoice. + */ +export interface TransactionPeerPullDebit extends TransactionCommon { + type: TransactionType.PeerPullDebit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + amountRaw: AmountString; + + amountEffective: AmountString; +} + +/** + * We sent money via a P2P payment. + */ +export interface TransactionPeerPushDebit extends TransactionCommon { + type: TransactionType.PeerPushDebit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; + + /** + * URI to accept the payment. + * + * Only present if the transaction is in a state where the other party can + * accept the payment. + */ + talerUri?: string; +} + +/** + * We received money via a P2P payment. + */ +export interface TransactionPeerPushCredit extends TransactionCommon { + type: TransactionType.PeerPushCredit; + + info: PeerInfoShort; + /** + * Exchange used. + */ + exchangeBaseUrl: string; + + /** + * Amount that got subtracted from the reserve balance. + */ + amountRaw: AmountString; + + /** + * Amount that actually was (or will be) added to the wallet's balance. + */ + amountEffective: AmountString; +} + +/** + * The exchange revoked a key and the wallet recoups funds. + */ +export interface TransactionRecoup extends TransactionCommon { + type: TransactionType.Recoup; +} + +export enum PaymentStatus { + /** + * Explicitly aborted after timeout / failure + */ + Aborted = "aborted", + + /** + * Payment failed, wallet will auto-retry. + * User should be given the option to retry now / abort. + */ + Failed = "failed", + + /** + * Paid successfully + */ + Paid = "paid", + + /** + * User accepted, payment is processing. + */ + Accepted = "accepted", +} + +export interface TransactionPayment extends TransactionCommon { + type: TransactionType.Payment; + + /** + * Additional information about the payment. + */ + info: OrderShortInfo; + + /** + * Wallet-internal end-to-end identifier for the payment. + */ + proposalId: string; + + /** + * Amount that must be paid for the contract + */ + amountRaw: AmountString; + + /** + * Amount that was paid, including deposit, wire and refresh fees. + */ + amountEffective: AmountString; + + /** + * Amount that has been refunded by the merchant + */ + totalRefundRaw: AmountString; + + /** + * Amount will be added to the wallet's balance after fees and refreshing + */ + totalRefundEffective: AmountString; + + /** + * Amount pending to be picked up + */ + refundPending: AmountString | undefined; + + /** + * Reference to applied refunds + */ + refunds: RefundInfoShort[]; + + /** + * Is the wallet currently checking for a refund? + */ + refundQueryActive: boolean; + + /** + * Does this purchase has an pos validation + */ + posConfirmation: string | undefined; +} + +export interface OrderShortInfo { + /** + * Order ID, uniquely identifies the order within a merchant instance + */ + orderId: string; + + /** + * Hash of the contract terms. + */ + contractTermsHash: string; + + /** + * More information about the merchant + */ + merchant: MerchantInfo; + + /** + * Summary of the order, given by the merchant + */ + summary: string; + + /** + * Map from IETF BCP 47 language tags to localized summaries + */ + summary_i18n?: InternationalizedString; + + /** + * URL of the fulfillment, given by the merchant + */ + fulfillmentUrl?: string; + + /** + * Plain text message that should be shown to the user + * when the payment is complete. + */ + fulfillmentMessage?: string; + + /** + * Translations of fulfillmentMessage. + */ + fulfillmentMessage_i18n?: InternationalizedString; +} + +export interface RefundInfoShort { + transactionId: string; + timestamp: TalerProtocolTimestamp; + amountEffective: AmountString; + amountRaw: AmountString; +} + +/** + * Summary information about the payment that we got a refund for. + */ +export interface RefundPaymentInfo { + summary: string; + summary_i18n?: InternationalizedString; + /** + * More information about the merchant + */ + merchant: MerchantInfo; +} + +export interface TransactionRefund extends TransactionCommon { + type: TransactionType.Refund; + + // Amount that has been refunded by the merchant + amountRaw: AmountString; + + // Amount will be added to the wallet's balance after fees and refreshing + amountEffective: AmountString; + + // ID for the transaction that is refunded + refundedTransactionId: string; + + paymentInfo: RefundPaymentInfo | undefined; +} + +/** + * A transaction shown for refreshes. + * Only shown for (1) refreshes not associated with other transactions + * and (2) refreshes in an error state. + */ +export interface TransactionRefresh extends TransactionCommon { + type: TransactionType.Refresh; + + refreshReason: RefreshReason; + + /** + * Transaction ID that caused this refresh. + */ + originatingTransactionId?: string; + + /** + * Always zero for refreshes + */ + amountRaw: AmountString; + + /** + * Fees, i.e. the effective, negative effect of the refresh + * on the balance. + * + * Only applicable for stand-alone refreshes, and zero for + * other refreshes where the transaction itself accounts for the + * refresh fee. + */ + amountEffective: AmountString; + + refreshInputAmount: AmountString; + refreshOutputAmount: AmountString; +} + +export interface DepositTransactionTrackingState { + // Raw wire transfer identifier of the deposit. + wireTransferId: string; + // When was the wire transfer given to the bank. + timestampExecuted: TalerProtocolTimestamp; + // Total amount transfer for this wtid (including fees) + amountRaw: AmountString; + // Wire fee amount for this exchange + wireFee: AmountString; +} + +/** + * Deposit transaction, which effectively sends + * money from this wallet somewhere else. + */ +export interface TransactionDeposit extends TransactionCommon { + type: TransactionType.Deposit; + + depositGroupId: string; + + /** + * Target for the deposit. + */ + targetPaytoUri: string; + + /** + * Raw amount that is being deposited + */ + amountRaw: AmountString; + + /** + * Effective amount that is being deposited + */ + amountEffective: AmountString; + + wireTransferDeadline: TalerProtocolTimestamp; + + wireTransferProgress: number; + + /** + * Did all the deposit requests succeed? + */ + deposited: boolean; + + trackingState: Array<DepositTransactionTrackingState>; +} + +export interface TransactionByIdRequest { + transactionId: string; +} + +export const codecForTransactionByIdRequest = + (): Codec<TransactionByIdRequest> => + buildCodecForObject<TransactionByIdRequest>() + .property("transactionId", codecForString()) + .build("TransactionByIdRequest"); + +export interface WithdrawalTransactionByURIRequest { + talerWithdrawUri: string; +} + +export const codecForWithdrawalTransactionByURIRequest = + (): Codec<WithdrawalTransactionByURIRequest> => + buildCodecForObject<WithdrawalTransactionByURIRequest>() + .property("talerWithdrawUri", codecForString()) + .build("WithdrawalTransactionByURIRequest"); + +export const codecForTransactionsRequest = (): Codec<TransactionsRequest> => + buildCodecForObject<TransactionsRequest>() + .property("currency", codecOptional(codecForString())) + .property("scopeInfo", codecOptional(codecForScopeInfo())) + .property("search", codecOptional(codecForString())) + .property( + "sort", + codecOptional( + codecForEither( + codecForConstString("ascending"), + codecForConstString("descending"), + codecForConstString("stable-ascending"), + ), + ), + ) + .property("includeRefreshes", codecOptional(codecForBoolean())) + .build("TransactionsRequest"); + +// FIXME: do full validation here! +export const codecForTransactionsResponse = (): Codec<TransactionsResponse> => + buildCodecForObject<TransactionsResponse>() + .property("transactions", codecForList(codecForAny())) + .build("TransactionsResponse"); + +export const codecForOrderShortInfo = (): Codec<OrderShortInfo> => + buildCodecForObject<OrderShortInfo>() + .property("contractTermsHash", codecForString()) + .property("fulfillmentMessage", codecOptional(codecForString())) + .property( + "fulfillmentMessage_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("fulfillmentUrl", codecOptional(codecForString())) + .property("merchant", codecForMerchantInfo()) + .property("orderId", codecForString()) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .build("OrderShortInfo"); + +export interface ListAssociatedRefreshesRequest { + transactionId: string; +} + +export const codecForListAssociatedRefreshesRequest = + (): Codec<ListAssociatedRefreshesRequest> => + buildCodecForObject<ListAssociatedRefreshesRequest>() + .property("transactionId", codecForString()) + .build("ListAssociatedRefreshesRequest"); + +export interface ListAssociatedRefreshesResponse { + transactionIds: string[]; +} diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -0,0 +1,3468 @@ +/* + This file is part of GNU Taler + (C) 2019-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Types used by clients of the wallet. + * + * These types are defined in a separate file make tree shaking easier, since + * some components use these types (via RPC) but do not depend on the wallet + * code directly. + * + * @author Florian Dold <dold@taler.net> + */ + +/** + * Imports. + */ +import { AmountJson, codecForAmountString } from "./amounts.js"; +import { + Codec, + Context, + DecodingError, + buildCodecForObject, + buildCodecForUnion, + codecForAny, + codecForBoolean, + codecForConstString, + codecForEither, + codecForList, + codecForMap, + codecForNumber, + codecForString, + codecOptional, + renderContext, +} from "./codec.js"; +import { + AmountString, + CurrencySpecification, + EddsaPrivateKeyString, + InternationalizedString, + TalerMerchantApi, + TemplateParams, + WithdrawalOperationStatus, + canonicalizeBaseUrl, +} from "./index.js"; +import { VersionMatchResult } from "./libtool-version.js"; +import { PaytoString, PaytoUri, codecForPaytoString } from "./payto.js"; +import { QrCodeSpec } from "./qr.js"; +import { AgeCommitmentProof } from "./taler-crypto.js"; +import { TalerErrorCode } from "./taler-error-codes.js"; +import { + AbsoluteTime, + TalerPreciseTimestamp, + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForAbsoluteTime, + codecForPreciseTimestamp, + codecForTimestamp, +} from "./time.js"; +import { + AccountRestriction, + AuditorDenomSig, + CoinEnvelope, + DenomKeyType, + DenominationPubKey, + ExchangeAuditor, + ExchangeWireAccount, + PeerContractTerms, + UnblindedSignature, + codecForExchangeWireAccount, + codecForPeerContractTerms, +} from "./types-taler-exchange.js"; +import { + MerchantContractTerms, + MerchantInfo, + codecForMerchantContractTerms, +} from "./types-taler-merchant.js"; +import { BackupRecovery } from "./types-taler-sync.js"; +import { + OrderShortInfo, + TransactionState, + TransactionType, +} from "./types-taler-wallet-transactions.js"; + +/** + * Identifier for a transaction in the wallet. + */ +declare const __txId: unique symbol; +export type TransactionIdStr = `txn:${string}:${string}` & { [__txId]: true }; + +/** + * Identifier for a pending task in the wallet. + */ +declare const __pndId: unique symbol; +export type PendingIdStr = `pnd:${string}:${string}` & { [__pndId]: true }; + +declare const __tmbId: unique symbol; +export type TombstoneIdStr = `tmb:${string}:${string}` & { [__tmbId]: true }; + +function codecForTransactionIdStr(): Codec<TransactionIdStr> { + return { + decode(x: any, c?: Context): TransactionIdStr { + if (typeof x === "string" && x.startsWith("txn:")) { + return x as TransactionIdStr; + } + throw new DecodingError( + `expected string starting with "txn:" at ${renderContext( + c, + )} but got ${x}`, + ); + }, + }; +} + +function codecForPendingIdStr(): Codec<PendingIdStr> { + return { + decode(x: any, c?: Context): PendingIdStr { + if (typeof x === "string" && x.startsWith("txn:")) { + return x as PendingIdStr; + } + throw new DecodingError( + `expected string starting with "txn:" at ${renderContext( + c, + )} but got ${x}`, + ); + }, + }; +} + +function codecForTombstoneIdStr(): Codec<TombstoneIdStr> { + return { + decode(x: any, c?: Context): TombstoneIdStr { + if (typeof x === "string" && x.startsWith("tmb:")) { + return x as TombstoneIdStr; + } + throw new DecodingError( + `expected string starting with "tmb:" at ${renderContext( + c, + )} but got ${x}`, + ); + }, + }; +} + +export function codecForCanonBaseUrl(): Codec<string> { + return { + decode(x: any, c?: Context): string { + if (typeof x === "string") { + const canon = canonicalizeBaseUrl(x); + if (x !== canon) { + throw new DecodingError( + `expected canonicalized base URL at ${renderContext( + c, + )} but got value '${x}'`, + ); + } + return x; + } + throw new DecodingError( + `expected base URL at ${renderContext(c)} but got type ${typeof x}`, + ); + }, + }; +} + +/** + * Response for the create reserve request to the wallet. + */ +export class CreateReserveResponse { + /** + * Exchange URL where the bank should create the reserve. + * The URL is canonicalized in the response. + */ + exchange: string; + + /** + * Reserve public key of the newly created reserve. + */ + reservePub: string; +} + +export interface GetBalanceDetailRequest { + currency: string; +} + +export const codecForGetBalanceDetailRequest = + (): Codec<GetBalanceDetailRequest> => + buildCodecForObject<GetBalanceDetailRequest>() + .property("currency", codecForString()) + .build("GetBalanceDetailRequest"); + +/** + * How the amount should be interpreted in a transaction + * Effective = how the balance is change + * Raw = effective amount without fee + * + * Depending on the transaction, raw can be higher than effective + */ +export enum TransactionAmountMode { + Effective = "effective", + Raw = "raw", +} + +export type GetPlanForOperationRequest = + | GetPlanForWithdrawRequest + | GetPlanForDepositRequest; +// | GetPlanForPushDebitRequest +// | GetPlanForPullCreditRequest +// | GetPlanForPaymentRequest +// | GetPlanForTipRequest +// | GetPlanForRefundRequest +// | GetPlanForPullDebitRequest +// | GetPlanForPushCreditRequest; + +interface GetPlanForWalletInitiatedOperation { + instructedAmount: AmountString; + mode: TransactionAmountMode; +} + +export interface ConvertAmountRequest { + amount: AmountString; + type: TransactionAmountMode; + depositPaytoUri: PaytoString; +} + +export const codecForConvertAmountRequest = + buildCodecForObject<ConvertAmountRequest>() + .property("amount", codecForAmountString()) + .property("depositPaytoUri", codecForPaytoString()) + .property( + "type", + codecForEither( + codecForConstString(TransactionAmountMode.Raw), + codecForConstString(TransactionAmountMode.Effective), + ), + ) + .build("ConvertAmountRequest"); + +export interface GetAmountRequest { + currency: string; +} + +export const codecForGetAmountRequest = buildCodecForObject<GetAmountRequest>() + .property("currency", codecForString()) + .build("GetAmountRequest"); + +interface GetPlanToCompleteOperation { + instructedAmount: AmountString; +} + +const codecForGetPlanForWalletInitiatedOperation = < + T extends GetPlanForWalletInitiatedOperation, +>() => + buildCodecForObject<T>() + .property( + "mode", + codecForEither( + codecForConstString(TransactionAmountMode.Raw), + codecForConstString(TransactionAmountMode.Effective), + ), + ) + .property("instructedAmount", codecForAmountString()); + +interface GetPlanForWithdrawRequest extends GetPlanForWalletInitiatedOperation { + type: TransactionType.Withdrawal; + exchangeUrl?: string; +} +interface GetPlanForDepositRequest extends GetPlanForWalletInitiatedOperation { + type: TransactionType.Deposit; + account: string; //payto string +} +interface GetPlanForPushDebitRequest + extends GetPlanForWalletInitiatedOperation { + type: TransactionType.PeerPushDebit; +} + +interface GetPlanForPullCreditRequest + extends GetPlanForWalletInitiatedOperation { + type: TransactionType.PeerPullCredit; + exchangeUrl: string; +} + +const codecForGetPlanForWithdrawRequest = + codecForGetPlanForWalletInitiatedOperation<GetPlanForWithdrawRequest>() + .property("type", codecForConstString(TransactionType.Withdrawal)) + .property("exchangeUrl", codecOptional(codecForString())) + .build("GetPlanForWithdrawRequest"); + +const codecForGetPlanForDepositRequest = + codecForGetPlanForWalletInitiatedOperation<GetPlanForDepositRequest>() + .property("type", codecForConstString(TransactionType.Deposit)) + .property("account", codecForString()) + .build("GetPlanForDepositRequest"); + +const codecForGetPlanForPushDebitRequest = + codecForGetPlanForWalletInitiatedOperation<GetPlanForPushDebitRequest>() + .property("type", codecForConstString(TransactionType.PeerPushDebit)) + .build("GetPlanForPushDebitRequest"); + +const codecForGetPlanForPullCreditRequest = + codecForGetPlanForWalletInitiatedOperation<GetPlanForPullCreditRequest>() + .property("type", codecForConstString(TransactionType.PeerPullCredit)) + .property("exchangeUrl", codecForString()) + .build("GetPlanForPullCreditRequest"); + +interface GetPlanForPaymentRequest extends GetPlanToCompleteOperation { + type: TransactionType.Payment; + wireMethod: string; + ageRestriction: number; + maxDepositFee: AmountString; +} + +interface GetPlanForPullDebitRequest extends GetPlanToCompleteOperation { + type: TransactionType.PeerPullDebit; +} + +interface GetPlanForPushCreditRequest extends GetPlanToCompleteOperation { + type: TransactionType.PeerPushCredit; +} + +const codecForGetPlanForPaymentRequest = + buildCodecForObject<GetPlanForPaymentRequest>() + .property("type", codecForConstString(TransactionType.Payment)) + .property("maxDepositFee", codecForAmountString()) + .build("GetPlanForPaymentRequest"); + +const codecForGetPlanForPullDebitRequest = + buildCodecForObject<GetPlanForPullDebitRequest>() + .property("type", codecForConstString(TransactionType.PeerPullDebit)) + .build("GetPlanForPullDebitRequest"); + +const codecForGetPlanForPushCreditRequest = + buildCodecForObject<GetPlanForPushCreditRequest>() + .property("type", codecForConstString(TransactionType.PeerPushCredit)) + .build("GetPlanForPushCreditRequest"); + +export const codecForGetPlanForOperationRequest = + (): Codec<GetPlanForOperationRequest> => + buildCodecForUnion<GetPlanForOperationRequest>() + .discriminateOn("type") + .alternative( + TransactionType.Withdrawal, + codecForGetPlanForWithdrawRequest, + ) + .alternative(TransactionType.Deposit, codecForGetPlanForDepositRequest) + // .alternative( + // TransactionType.PeerPushDebit, + // codecForGetPlanForPushDebitRequest, + // ) + // .alternative( + // TransactionType.PeerPullCredit, + // codecForGetPlanForPullCreditRequest, + // ) + // .alternative(TransactionType.Payment, codecForGetPlanForPaymentRequest) + // .alternative( + // TransactionType.PeerPullDebit, + // codecForGetPlanForPullDebitRequest, + // ) + // .alternative( + // TransactionType.PeerPushCredit, + // codecForGetPlanForPushCreditRequest, + // ) + .build("GetPlanForOperationRequest"); + +export interface GetPlanForOperationResponse { + effectiveAmount: AmountString; + rawAmount: AmountString; + counterPartyAmount?: AmountString; + details: any; +} + +export const codecForGetPlanForOperationResponse = + (): Codec<GetPlanForOperationResponse> => + buildCodecForObject<GetPlanForOperationResponse>() + .property("effectiveAmount", codecForAmountString()) + .property("rawAmount", codecForAmountString()) + .property("details", codecForAny()) + .property("counterPartyAmount", codecOptional(codecForAmountString())) + .build("GetPlanForOperationResponse"); + +export interface AmountResponse { + effectiveAmount: AmountString; + rawAmount: AmountString; +} + +export const codecForAmountResponse = (): Codec<AmountResponse> => + buildCodecForObject<AmountResponse>() + .property("effectiveAmount", codecForAmountString()) + .property("rawAmount", codecForAmountString()) + .build("AmountResponse"); + +export enum BalanceFlag { + IncomingKyc = "incoming-kyc", + IncomingAml = "incoming-aml", + IncomingConfirmation = "incoming-confirmation", + OutgoingKyc = "outgoing-kyc", +} + +export interface WalletBalance { + scopeInfo: ScopeInfo; + available: AmountString; + pendingIncoming: AmountString; + pendingOutgoing: AmountString; + + /** + * Does the balance for this currency have a pending + * transaction? + * + * @deprecated use flags and pendingIncoming/pendingOutgoing instead + */ + hasPendingTransactions: boolean; + + /** + * Is there a transaction that requires user input? + * + * @deprecated use flags instead + */ + requiresUserInput: boolean; + + flags: BalanceFlag[]; +} + +export const codecForScopeInfoGlobal = (): Codec<ScopeInfoGlobal> => + buildCodecForObject<ScopeInfoGlobal>() + .property("currency", codecForString()) + .property("type", codecForConstString(ScopeType.Global)) + .build("ScopeInfoGlobal"); + +export const codecForScopeInfoExchange = (): Codec<ScopeInfoExchange> => + buildCodecForObject<ScopeInfoExchange>() + .property("currency", codecForString()) + .property("type", codecForConstString(ScopeType.Exchange)) + .property("url", codecForString()) + .build("ScopeInfoExchange"); + +export const codecForScopeInfoAuditor = (): Codec<ScopeInfoAuditor> => + buildCodecForObject<ScopeInfoAuditor>() + .property("currency", codecForString()) + .property("type", codecForConstString(ScopeType.Auditor)) + .property("url", codecForString()) + .build("ScopeInfoAuditor"); + +export const codecForScopeInfo = (): Codec<ScopeInfo> => + buildCodecForUnion<ScopeInfo>() + .discriminateOn("type") + .alternative(ScopeType.Global, codecForScopeInfoGlobal()) + .alternative(ScopeType.Exchange, codecForScopeInfoExchange()) + .alternative(ScopeType.Auditor, codecForScopeInfoAuditor()) + .build("ScopeInfo"); + +export interface GetCurrencySpecificationRequest { + scope: ScopeInfo; +} + +export const codecForGetCurrencyInfoRequest = + (): Codec<GetCurrencySpecificationRequest> => + buildCodecForObject<GetCurrencySpecificationRequest>() + .property("scope", codecForScopeInfo()) + .build("GetCurrencySpecificationRequest"); + +export interface ListExchangesForScopedCurrencyRequest { + scope: ScopeInfo; +} + +export const codecForListExchangesForScopedCurrencyRequest = + (): Codec<ListExchangesForScopedCurrencyRequest> => + buildCodecForObject<ListExchangesForScopedCurrencyRequest>() + .property("scope", codecForScopeInfo()) + .build("ListExchangesForScopedCurrencyRequest"); + +export interface GetCurrencySpecificationResponse { + currencySpecification: CurrencySpecification; +} + +export interface BuiltinExchange { + exchangeBaseUrl: string; + currencyHint: string; +} + +export interface PartialWalletRunConfig { + builtin?: Partial<WalletRunConfig["builtin"]>; + testing?: Partial<WalletRunConfig["testing"]>; + features?: Partial<WalletRunConfig["features"]>; + lazyTaskLoop?: Partial<WalletRunConfig["lazyTaskLoop"]>; +} + +export interface WalletRunConfig { + /** + * Initialization values useful for a complete startup. + * + * These are values may be overridden by different wallets + */ + builtin: { + exchanges: BuiltinExchange[]; + }; + + /** + * Unsafe options which it should only be used to create + * testing environment. + */ + testing: { + /** + * Allow withdrawal of denominations even though they are about to expire. + */ + denomselAllowLate: boolean; + devModeActive: boolean; + insecureTrustExchange: boolean; + preventThrottling: boolean; + skipDefaults: boolean; + emitObservabilityEvents?: boolean; + }; + + /** + * Configurations values that may be safe to show to the user + */ + features: { + allowHttp: boolean; + }; + + /** + * Start processing tasks only when explicitly required, even after + * init has been called. + * + * Useful when the wallet is started to make single read-only request, + * as otherwise wallet-core starts making network request and process + * unrelated pending tasks. + */ + lazyTaskLoop: boolean; +} + +export interface InitRequest { + config?: PartialWalletRunConfig; +} + +export const codecForInitRequest = (): Codec<InitRequest> => + buildCodecForObject<InitRequest>() + .property("config", codecForAny()) + .build("InitRequest"); + +export interface InitResponse { + versionInfo: WalletCoreVersion; +} + +export enum ScopeType { + Global = "global", + Exchange = "exchange", + Auditor = "auditor", +} + +export type ScopeInfoGlobal = { type: ScopeType.Global; currency: string }; + +export type ScopeInfoExchange = { + type: ScopeType.Exchange; + currency: string; + url: string; +}; + +export type ScopeInfoAuditor = { + type: ScopeType.Auditor; + currency: string; + url: string; +}; + +export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; + +/** + * Encode scope info as a string. + * + * Format must be stable as it's used in the database. + */ +export function stringifyScopeInfo(si: ScopeInfo): string { + switch (si.type) { + case ScopeType.Global: + return `taler-si:global/${si.currency}}`; + case ScopeType.Auditor: + return `taler-si:auditor/${si.currency}/${encodeURIComponent(si.url)}`; + case ScopeType.Exchange: + return `taler-si:exchange/${si.currency}/${encodeURIComponent(si.url)}`; + } +} + +export interface BalancesResponse { + balances: WalletBalance[]; +} + +export const codecForBalance = (): Codec<WalletBalance> => + buildCodecForObject<WalletBalance>() + .property("scopeInfo", codecForAny()) // FIXME + .property("available", codecForAmountString()) + .property("hasPendingTransactions", codecForBoolean()) + .property("pendingIncoming", codecForAmountString()) + .property("pendingOutgoing", codecForAmountString()) + .property("requiresUserInput", codecForBoolean()) + .property("flags", codecForAny()) // FIXME + .build("Balance"); + +export const codecForBalancesResponse = (): Codec<BalancesResponse> => + buildCodecForObject<BalancesResponse>() + .property("balances", codecForList(codecForBalance())) + .build("BalancesResponse"); + +/** + * For terseness. + */ +export function mkAmount( + value: number, + fraction: number, + currency: string, +): AmountJson { + return { value, fraction, currency }; +} + +/** + * Status of a coin. + */ +export enum CoinStatus { + /** + * Withdrawn and never shown to anybody. + */ + Fresh = "fresh", + + /** + * Coin was lost as the denomination is not usable anymore. + */ + DenomLoss = "denom-loss", + + /** + * Fresh, but currently marked as "suspended", thus won't be used + * for spending. Used for testing. + */ + FreshSuspended = "fresh-suspended", + + /** + * A coin that has been spent and refreshed. + */ + Dormant = "dormant", +} + +export type WalletCoinHistoryItem = + | { + type: "withdraw"; + transactionId: TransactionIdStr; + } + | { + type: "spend"; + transactionId: TransactionIdStr; + amount: AmountString; + } + | { + type: "refresh"; + transactionId: TransactionIdStr; + amount: AmountString; + } + | { + type: "recoup"; + transactionId: TransactionIdStr; + amount: AmountString; + } + | { + type: "refund"; + transactionId: TransactionIdStr; + amount: AmountString; + }; + +/** + * Easy to process format for the public data of coins + * managed by the wallet. + */ +export interface CoinDumpJson { + coins: Array<{ + /** + * The coin's denomination's public key. + */ + denomPub: DenominationPubKey; + /** + * Hash of denom_pub. + */ + denomPubHash: string; + /** + * Value of the denomination (without any fees). + */ + denomValue: string; + /** + * Public key of the coin. + */ + coinPub: string; + /** + * Base URL of the exchange for the coin. + */ + exchangeBaseUrl: string; + /** + * Public key of the parent coin. + * Only present if this coin was obtained via refreshing. + */ + refreshParentCoinPub: string | undefined; + /** + * Public key of the reserve for this coin. + * Only present if this coin was obtained via refreshing. + */ + withdrawalReservePub: string | undefined; + /** + * Status of the coin. + */ + coinStatus: CoinStatus; + /** + * Information about the age restriction + */ + ageCommitmentProof: AgeCommitmentProof | undefined; + history: WalletCoinHistoryItem[]; + }>; +} + +export enum ConfirmPayResultType { + Done = "done", + Pending = "pending", +} + +/** + * Result for confirmPay + */ +export interface ConfirmPayResultDone { + type: ConfirmPayResultType.Done; + contractTerms: MerchantContractTerms; + transactionId: TransactionIdStr; +} + +export interface ConfirmPayResultPending { + type: ConfirmPayResultType.Pending; + transactionId: TransactionIdStr; + lastError: TalerErrorDetail | undefined; +} + +export const codecForTalerErrorDetail = (): Codec<TalerErrorDetail> => + buildCodecForObject<TalerErrorDetail>() + .property("code", codecForNumber()) + .property("when", codecOptional(codecForAbsoluteTime)) + .property("hint", codecOptional(codecForString())) + .build("TalerErrorDetail"); + +export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending; + +export const codecForConfirmPayResultPending = + (): Codec<ConfirmPayResultPending> => + buildCodecForObject<ConfirmPayResultPending>() + .property("lastError", codecOptional(codecForTalerErrorDetail())) + .property("transactionId", codecForTransactionIdStr()) + .property("type", codecForConstString(ConfirmPayResultType.Pending)) + .build("ConfirmPayResultPending"); + +export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> => + buildCodecForObject<ConfirmPayResultDone>() + .property("type", codecForConstString(ConfirmPayResultType.Done)) + .property("transactionId", codecForTransactionIdStr()) + .property("contractTerms", codecForMerchantContractTerms()) + .build("ConfirmPayResultDone"); + +export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> => + buildCodecForUnion<ConfirmPayResult>() + .discriminateOn("type") + .alternative( + ConfirmPayResultType.Pending, + codecForConfirmPayResultPending(), + ) + .alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone()) + .build("ConfirmPayResult"); + +/** + * Information about all sender wire details known to the wallet, + * as well as exchanges that accept these wire types. + */ +export interface SenderWireInfos { + /** + * Mapping from exchange base url to list of accepted + * wire types. + */ + exchangeWireTypes: { [exchangeBaseUrl: string]: string[] }; + + /** + * Sender wire information stored in the wallet. + */ + senderWires: string[]; +} + +/** + * Request to mark a reserve as confirmed. + */ +export interface ConfirmReserveRequest { + /** + * Public key of then reserve that should be marked + * as confirmed. + */ + reservePub: string; +} + +export const codecForConfirmReserveRequest = (): Codec<ConfirmReserveRequest> => + buildCodecForObject<ConfirmReserveRequest>() + .property("reservePub", codecForString()) + .build("ConfirmReserveRequest"); + +export interface PrepareRefundResult { + proposalId: string; + + effectivePaid: AmountString; + gone: AmountString; + granted: AmountString; + pending: boolean; + awaiting: AmountString; + + info: OrderShortInfo; +} + +export interface BenchmarkResult { + time: { [s: string]: number }; + repetitions: number; +} + +export enum PreparePayResultType { + PaymentPossible = "payment-possible", + InsufficientBalance = "insufficient-balance", + AlreadyConfirmed = "already-confirmed", +} + +export const codecForPreparePayResultPaymentPossible = + (): Codec<PreparePayResultPaymentPossible> => + buildCodecForObject<PreparePayResultPaymentPossible>() + .property("amountEffective", codecForAmountString()) + .property("amountRaw", codecForAmountString()) + .property("contractTerms", codecForMerchantContractTerms()) + .property("transactionId", codecForTransactionIdStr()) + .property("proposalId", codecForString()) + .property("contractTermsHash", codecForString()) + .property("talerUri", codecForString()) + .property( + "status", + codecForConstString(PreparePayResultType.PaymentPossible), + ) + .build("PreparePayResultPaymentPossible"); + +export interface BalanceDetails {} + +/** + * Detailed reason for why the wallet's balance is insufficient. + */ +export interface PaymentInsufficientBalanceDetails { + /** + * Amount requested by the merchant. + */ + amountRequested: AmountString; + + /** + * Balance of type "available" (see balance.ts for definition). + */ + balanceAvailable: AmountString; + + /** + * Balance of type "material" (see balance.ts for definition). + */ + balanceMaterial: AmountString; + + /** + * Balance of type "age-acceptable" (see balance.ts for definition). + */ + balanceAgeAcceptable: AmountString; + + /** + * Balance of type "merchant-acceptable" (see balance.ts for definition). + */ + balanceReceiverAcceptable: AmountString; + + /** + * Balance of type "merchant-depositable" (see balance.ts for definition). + */ + balanceReceiverDepositable: AmountString; + + balanceExchangeDepositable: AmountString; + + /** + * Maximum effective amount that the wallet can spend, + * when all fees are paid by the wallet. + */ + maxEffectiveSpendAmount: AmountString; + + perExchange: { + [url: string]: { + balanceAvailable: AmountString; + balanceMaterial: AmountString; + balanceExchangeDepositable: AmountString; + balanceAgeAcceptable: AmountString; + balanceReceiverAcceptable: AmountString; + balanceReceiverDepositable: AmountString; + maxEffectiveSpendAmount: AmountString; + /** + * Exchange doesn't have global fees configured for the relevant year, + * p2p payments aren't possible. + */ + missingGlobalFees: boolean; + }; + }; +} + +export const codecForPayMerchantInsufficientBalanceDetails = + (): Codec<PaymentInsufficientBalanceDetails> => + buildCodecForObject<PaymentInsufficientBalanceDetails>() + .property("amountRequested", codecForAmountString()) + .property("balanceAgeAcceptable", codecForAmountString()) + .property("balanceAvailable", codecForAmountString()) + .property("balanceMaterial", codecForAmountString()) + .property("balanceReceiverAcceptable", codecForAmountString()) + .property("balanceReceiverDepositable", codecForAmountString()) + .property("balanceExchangeDepositable", codecForAmountString()) + .property("perExchange", codecForAny()) + .property("maxEffectiveSpendAmount", codecForAmountString()) + .build("PayMerchantInsufficientBalanceDetails"); + +export const codecForPreparePayResultInsufficientBalance = + (): Codec<PreparePayResultInsufficientBalance> => + buildCodecForObject<PreparePayResultInsufficientBalance>() + .property("amountRaw", codecForAmountString()) + .property("contractTerms", codecForAny()) + .property("talerUri", codecForString()) + .property("proposalId", codecForString()) + .property("transactionId", codecForTransactionIdStr()) + .property( + "status", + codecForConstString(PreparePayResultType.InsufficientBalance), + ) + .property( + "balanceDetails", + codecForPayMerchantInsufficientBalanceDetails(), + ) + .build("PreparePayResultInsufficientBalance"); + +export const codecForPreparePayResultAlreadyConfirmed = + (): Codec<PreparePayResultAlreadyConfirmed> => + buildCodecForObject<PreparePayResultAlreadyConfirmed>() + .property( + "status", + codecForConstString(PreparePayResultType.AlreadyConfirmed), + ) + .property("amountEffective", codecOptional(codecForAmountString())) + .property("amountRaw", codecForAmountString()) + .property("paid", codecForBoolean()) + .property("talerUri", codecForString()) + .property("contractTerms", codecForAny()) + .property("contractTermsHash", codecForString()) + .property("transactionId", codecForTransactionIdStr()) + .property("proposalId", codecForString()) + .build("PreparePayResultAlreadyConfirmed"); + +export const codecForPreparePayResult = (): Codec<PreparePayResult> => + buildCodecForUnion<PreparePayResult>() + .discriminateOn("status") + .alternative( + PreparePayResultType.AlreadyConfirmed, + codecForPreparePayResultAlreadyConfirmed(), + ) + .alternative( + PreparePayResultType.InsufficientBalance, + codecForPreparePayResultInsufficientBalance(), + ) + .alternative( + PreparePayResultType.PaymentPossible, + codecForPreparePayResultPaymentPossible(), + ) + .build("PreparePayResult"); + +/** + * Result of a prepare pay operation. + */ +export type PreparePayResult = + | PreparePayResultInsufficientBalance + | PreparePayResultAlreadyConfirmed + | PreparePayResultPaymentPossible; + +/** + * Payment is possible. + */ +export interface PreparePayResultPaymentPossible { + status: PreparePayResultType.PaymentPossible; + transactionId: TransactionIdStr; + /** + * @deprecated use transactionId instead + */ + proposalId: string; + contractTerms: MerchantContractTerms; + contractTermsHash: string; + amountRaw: AmountString; + amountEffective: AmountString; + talerUri: string; +} + +export interface PreparePayResultInsufficientBalance { + status: PreparePayResultType.InsufficientBalance; + transactionId: TransactionIdStr; + /** + * @deprecated use transactionId + */ + proposalId: string; + contractTerms: MerchantContractTerms; + amountRaw: AmountString; + talerUri: string; + balanceDetails: PaymentInsufficientBalanceDetails; +} + +export interface PreparePayResultAlreadyConfirmed { + status: PreparePayResultType.AlreadyConfirmed; + transactionId: TransactionIdStr; + contractTerms: MerchantContractTerms; + paid: boolean; + amountRaw: AmountString; + amountEffective: AmountString | undefined; + contractTermsHash: string; + /** + * @deprecated use transactionId + */ + proposalId: string; + talerUri: string; +} + +export interface BankWithdrawDetails { + status: WithdrawalOperationStatus; + currency: string; + amount: AmountJson | undefined; + editableAmount: boolean; + maxAmount: AmountJson | undefined; + wireFee: AmountJson | undefined; + senderWire?: string; + exchange?: string; + editableExchange: boolean; + confirmTransferUrl?: string; + wireTypes: string[]; + operationId: string; + apiBaseUrl: string; +} + +export interface AcceptWithdrawalResponse { + reservePub: string; + confirmTransferUrl?: string; + transactionId: TransactionIdStr; +} + +/** + * Details about a purchase, including refund status. + */ +export interface PurchaseDetails { + contractTerms: Record<string, undefined>; + hasRefund: boolean; + totalRefundAmount: AmountJson; + totalRefundAndRefreshFees: AmountJson; +} + +export interface WalletDiagnostics { + walletManifestVersion: string; + walletManifestDisplayVersion: string; + errors: string[]; + firefoxIdbProblem: boolean; + dbOutdated: boolean; +} + +export interface TalerErrorDetail { + code: TalerErrorCode; + when?: AbsoluteTime; + hint?: string; + [x: string]: unknown; +} + +/** + * Minimal information needed about a planchet for unblinding a signature. + * + * Can be a withdrawal/refresh planchet. + */ +export interface PlanchetUnblindInfo { + denomPub: DenominationPubKey; + blindingKey: string; +} + +export interface WithdrawalPlanchet { + coinPub: string; + coinPriv: string; + reservePub: string; + denomPubHash: string; + denomPub: DenominationPubKey; + blindingKey: string; + withdrawSig: string; + coinEv: CoinEnvelope; + coinValue: AmountJson; + coinEvHash: string; + ageCommitmentProof?: AgeCommitmentProof; +} + +export interface PlanchetCreationRequest { + secretSeed: string; + coinIndex: number; + value: AmountJson; + feeWithdraw: AmountJson; + denomPub: DenominationPubKey; + reservePub: string; + reservePriv: string; + restrictAge?: number; +} + +/** + * Reasons for why a coin is being refreshed. + */ +export enum RefreshReason { + Manual = "manual", + PayMerchant = "pay-merchant", + PayDeposit = "pay-deposit", + PayPeerPush = "pay-peer-push", + PayPeerPull = "pay-peer-pull", + Refund = "refund", + AbortPay = "abort-pay", + AbortDeposit = "abort-deposit", + AbortPeerPushDebit = "abort-peer-push-debit", + AbortPeerPullDebit = "abort-peer-pull-debit", + Recoup = "recoup", + BackupRestored = "backup-restored", + Scheduled = "scheduled", +} + +/** + * Request to refresh a single coin. + */ +export interface CoinRefreshRequest { + readonly coinPub: string; + readonly amount: AmountString; +} + +/** + * Private data required to make a deposit permission. + */ +export interface DepositInfo { + exchangeBaseUrl: string; + contractTermsHash: string; + coinPub: string; + coinPriv: string; + spendAmount: AmountJson; + timestamp: TalerProtocolTimestamp; + refundDeadline: TalerProtocolTimestamp; + merchantPub: string; + feeDeposit: AmountJson; + wireInfoHash: string; + denomKeyType: DenomKeyType; + denomPubHash: string; + denomSig: UnblindedSignature; + + requiredMinimumAge?: number; + + ageCommitmentProof?: AgeCommitmentProof; +} + +export interface ExchangesShortListResponse { + exchanges: ShortExchangeListItem[]; +} + +export interface ExchangesListResponse { + exchanges: ExchangeListItem[]; +} + +export interface ExchangeDetailedResponse { + exchange: ExchangeFullDetails; +} + +export interface WalletCoreVersion { + implementationSemver: string; + implementationGitHash: string; + + /** + * Wallet-core protocol version supported by this implementation + * of the API ("server" version). + */ + version: string; + exchange: string; + merchant: string; + + bankIntegrationApiRange: string; + bankConversionApiRange: string; + corebankApiRange: string; + + /** + * @deprecated as bank was split into multiple APIs with separate versioning + */ + bank: string; + + /** + * @deprecated + */ + hash: string | undefined; + + /** + * @deprecated will be removed + */ + devMode: boolean; +} + +export interface KnownBankAccountsInfo { + uri: PaytoUri; + kyc_completed: boolean; + currency: string; + alias: string; +} + +export interface KnownBankAccounts { + accounts: KnownBankAccountsInfo[]; +} + +/** + * Wire fee for one wire method + */ +export interface WireFee { + /** + * Fee for wire transfers. + */ + wireFee: AmountString; + + /** + * Fees to close and refund a reserve. + */ + closingFee: AmountString; + + /** + * Start date of the fee. + */ + startStamp: TalerProtocolTimestamp; + + /** + * End date of the fee. + */ + endStamp: TalerProtocolTimestamp; + + /** + * Signature made by the exchange master key. + */ + sig: string; +} + +export type WireFeeMap = { [wireMethod: string]: WireFee[] }; + +export interface WireInfo { + feesForType: WireFeeMap; + accounts: ExchangeWireAccount[]; +} + +export interface ExchangeGlobalFees { + startDate: TalerProtocolTimestamp; + endDate: TalerProtocolTimestamp; + + historyFee: AmountString; + accountFee: AmountString; + purseFee: AmountString; + + historyTimeout: TalerProtocolDuration; + purseTimeout: TalerProtocolDuration; + + purseLimit: number; + + signature: string; +} + +const codecForWireFee = (): Codec<WireFee> => + buildCodecForObject<WireFee>() + .property("sig", codecForString()) + .property("wireFee", codecForAmountString()) + .property("closingFee", codecForAmountString()) + .property("startStamp", codecForTimestamp) + .property("endStamp", codecForTimestamp) + .build("codecForWireFee"); + +const codecForWireInfo = (): Codec<WireInfo> => + buildCodecForObject<WireInfo>() + .property("feesForType", codecForMap(codecForList(codecForWireFee()))) + .property("accounts", codecForList(codecForExchangeWireAccount())) + .build("codecForWireInfo"); + +export interface DenominationInfo { + /** + * Value of one coin of the denomination. + */ + value: AmountString; + + /** + * Hash of the denomination public key. + * Stored in the database for faster lookups. + */ + denomPubHash: string; + + denomPub: DenominationPubKey; + + /** + * Fee for withdrawing. + */ + feeWithdraw: AmountString; + + /** + * Fee for depositing. + */ + feeDeposit: AmountString; + + /** + * Fee for refreshing. + */ + feeRefresh: AmountString; + + /** + * Fee for refunding. + */ + feeRefund: AmountString; + + /** + * Validity start date of the denomination. + */ + stampStart: TalerProtocolTimestamp; + + /** + * Date after which the currency can't be withdrawn anymore. + */ + stampExpireWithdraw: TalerProtocolTimestamp; + + /** + * Date after the denomination officially doesn't exist anymore. + */ + stampExpireLegal: TalerProtocolTimestamp; + + /** + * Data after which coins of this denomination can't be deposited anymore. + */ + stampExpireDeposit: TalerProtocolTimestamp; + + exchangeBaseUrl: string; +} + +export type DenomOperation = "deposit" | "withdraw" | "refresh" | "refund"; +export type DenomOperationMap<T> = { [op in DenomOperation]: T }; + +export interface FeeDescription { + group: string; + from: AbsoluteTime; + until: AbsoluteTime; + fee?: AmountString; +} + +export interface FeeDescriptionPair { + group: string; + from: AbsoluteTime; + until: AbsoluteTime; + left?: AmountString; + right?: AmountString; +} + +export interface TimePoint<T> { + id: string; + group: string; + fee: AmountString; + type: "start" | "end"; + moment: AbsoluteTime; + denom: T; +} + +export interface ExchangeFullDetails { + exchangeBaseUrl: string; + currency: string; + paytoUris: string[]; + auditors: ExchangeAuditor[]; + wireInfo: WireInfo; + denomFees: DenomOperationMap<FeeDescription[]>; + transferFees: Record<string, FeeDescription[]>; + globalFees: FeeDescription[]; +} + +export enum ExchangeTosStatus { + Pending = "pending", + Proposed = "proposed", + Accepted = "accepted", + MissingTos = "missing-tos", +} + +export enum ExchangeEntryStatus { + Preset = "preset", + Ephemeral = "ephemeral", + Used = "used", +} + +export enum ExchangeUpdateStatus { + Initial = "initial", + InitialUpdate = "initial-update", + Suspended = "suspended", + UnavailableUpdate = "unavailable-update", + Ready = "ready", + ReadyUpdate = "ready-update", +} + +export interface OperationErrorInfo { + error: TalerErrorDetail; +} + +export interface ShortExchangeListItem { + exchangeBaseUrl: string; +} + +/** + * Info about an exchange entry in the wallet. + */ +export interface ExchangeListItem { + exchangeBaseUrl: string; + masterPub: string | undefined; + currency: string; + paytoUris: string[]; + tosStatus: ExchangeTosStatus; + exchangeEntryStatus: ExchangeEntryStatus; + exchangeUpdateStatus: ExchangeUpdateStatus; + ageRestrictionOptions: number[]; + + /** + * P2P payments are disabled with this exchange + * (e.g. because no global fees are configured). + */ + peerPaymentsDisabled: boolean; + + /** + * Set to true if this exchange doesn't charge any fees. + */ + noFees: boolean; + + scopeInfo: ScopeInfo; + + lastUpdateTimestamp: TalerPreciseTimestamp | undefined; + + /** + * Information about the last error that occurred when trying + * to update the exchange info. + */ + lastUpdateErrorInfo?: OperationErrorInfo; + + unavailableReason?: TalerErrorDetail; +} + +const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => + buildCodecForObject<AuditorDenomSig>() + .property("denom_pub_h", codecForString()) + .property("auditor_sig", codecForString()) + .build("AuditorDenomSig"); + +const codecForExchangeAuditor = (): Codec<ExchangeAuditor> => + buildCodecForObject<ExchangeAuditor>() + .property("auditor_pub", codecForString()) + .property("auditor_url", codecForString()) + .property("denomination_keys", codecForList(codecForAuditorDenomSig())) + .build("codecForExchangeAuditor"); + +export const codecForFeeDescriptionPair = (): Codec<FeeDescriptionPair> => + buildCodecForObject<FeeDescriptionPair>() + .property("group", codecForString()) + .property("from", codecForAbsoluteTime) + .property("until", codecForAbsoluteTime) + .property("left", codecOptional(codecForAmountString())) + .property("right", codecOptional(codecForAmountString())) + .build("FeeDescriptionPair"); + +export const codecForFeeDescription = (): Codec<FeeDescription> => + buildCodecForObject<FeeDescription>() + .property("group", codecForString()) + .property("from", codecForAbsoluteTime) + .property("until", codecForAbsoluteTime) + .property("fee", codecOptional(codecForAmountString())) + .build("FeeDescription"); + +export const codecForFeesByOperations = (): Codec< + DenomOperationMap<FeeDescription[]> +> => + buildCodecForObject<DenomOperationMap<FeeDescription[]>>() + .property("deposit", codecForList(codecForFeeDescription())) + .property("withdraw", codecForList(codecForFeeDescription())) + .property("refresh", codecForList(codecForFeeDescription())) + .property("refund", codecForList(codecForFeeDescription())) + .build("DenomOperationMap"); + +export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> => + buildCodecForObject<ExchangeFullDetails>() + .property("currency", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("paytoUris", codecForList(codecForString())) + .property("auditors", codecForList(codecForExchangeAuditor())) + .property("wireInfo", codecForWireInfo()) + .property("denomFees", codecForFeesByOperations()) + .property( + "transferFees", + codecForMap(codecForList(codecForFeeDescription())), + ) + .property("globalFees", codecForList(codecForFeeDescription())) + .build("ExchangeFullDetails"); + +export const codecForExchangeListItem = (): Codec<ExchangeListItem> => + buildCodecForObject<ExchangeListItem>() + .property("currency", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("masterPub", codecOptional(codecForString())) + .property("paytoUris", codecForList(codecForString())) + .property("tosStatus", codecForAny()) + .property("exchangeEntryStatus", codecForAny()) + .property("exchangeUpdateStatus", codecForAny()) + .property("ageRestrictionOptions", codecForList(codecForNumber())) + .property("scopeInfo", codecForScopeInfo()) + .property("lastUpdateErrorInfo", codecForAny()) + .property("lastUpdateTimestamp", codecOptional(codecForPreciseTimestamp)) + .property("noFees", codecForBoolean()) + .property("peerPaymentsDisabled", codecForBoolean()) + .build("ExchangeListItem"); + +export const codecForExchangesListResponse = (): Codec<ExchangesListResponse> => + buildCodecForObject<ExchangesListResponse>() + .property("exchanges", codecForList(codecForExchangeListItem())) + .build("ExchangesListResponse"); + +export interface AcceptManualWithdrawalResult { + /** + * Payto URIs that can be used to fund the withdrawal. + * + * @deprecated in favor of withdrawalAccountsList + */ + exchangePaytoUris: string[]; + + /** + * Public key of the newly created reserve. + */ + reservePub: string; + + withdrawalAccountsList: WithdrawalExchangeAccountDetails[]; + + transactionId: TransactionIdStr; +} + +export interface WithdrawalDetailsForAmount { + /** + * Did the user accept the current version of the exchange's + * terms of service? + * + * @deprecated the client should query the exchange entry instead + */ + tosAccepted: boolean; + + /** + * Amount that the user will transfer to the exchange. + */ + amountRaw: AmountString; + + /** + * Amount that will be added to the user's wallet balance. + */ + amountEffective: AmountString; + + /** + * Number of coins that would be used for withdrawal. + * + * The UIs should warn if this number is too high (roughly at >100). + */ + numCoins: number; + + /** + * Ways to pay the exchange. + * + * @deprecated in favor of withdrawalAccountsList + */ + paytoUris: string[]; + + /** + * Ways to pay the exchange, including accounts that require currency conversion. + */ + withdrawalAccountsList: WithdrawalExchangeAccountDetails[]; + + /** + * If the exchange supports age-restricted coins it will return + * the array of ages. + */ + ageRestrictionOptions?: number[]; + + /** + * Scope info of the currency withdrawn. + */ + scopeInfo: ScopeInfo; +} + +export interface DenomSelItem { + denomPubHash: string; + count: number; + /** + * Number of denoms/planchets to skip, because + * a re-denomination effectively deleted them. + */ + skip?: number; +} + +/** + * Selected denominations withn some extra info. + */ +export interface DenomSelectionState { + totalCoinValue: AmountString; + totalWithdrawCost: AmountString; + selectedDenoms: DenomSelItem[]; + earliestDepositExpiration: TalerProtocolTimestamp; + hasDenomWithAgeRestriction: boolean; +} + +/** + * Information about what will happen doing a withdrawal. + * + * Sent to the wallet frontend to be rendered and shown to the user. + */ +export interface ExchangeWithdrawalDetails { + exchangePaytoUris: string[]; + + /** + * Filtered wire info to send to the bank. + */ + exchangeWireAccounts: string[]; + + exchangeCreditAccountDetails: WithdrawalExchangeAccountDetails[]; + + /** + * Selected denominations for withdraw. + */ + selectedDenoms: DenomSelectionState; + + /** + * Did the user already accept the current terms of service for the exchange? + */ + termsOfServiceAccepted: boolean; + + /** + * The earliest deposit expiration of the selected coins. + */ + earliestDepositExpiration: TalerProtocolTimestamp; + + /** + * Result of checking the wallet's version + * against the exchange's version. + * + * Older exchanges don't return version information. + */ + versionMatch: 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; + + /** + * Amount that will be subtracted from the reserve's balance. + */ + withdrawalAmountRaw: AmountString; + + /** + * Amount that will actually be added to the wallet's balance. + */ + withdrawalAmountEffective: AmountString; + + /** + * If the exchange supports age-restricted coins it will return + * the array of ages. + * + */ + ageRestrictionOptions?: number[]; + + scopeInfo: ScopeInfo; +} + +export interface GetExchangeTosResult { + /** + * Markdown version of the current ToS. + */ + content: string; + + /** + * Version tag of the current ToS. + */ + currentEtag: string; + + /** + * Version tag of the last ToS that the user has accepted, + * if any. + */ + acceptedEtag: string | undefined; + + /** + * Accepted content type + */ + contentType: string; + + /** + * Language of the returned content. + * + * If missing, language is unknown. + */ + contentLanguage: string | undefined; + + /** + * Available languages as advertised by the exchange. + */ + tosAvailableLanguages: string[]; + + tosStatus: ExchangeTosStatus; +} + +export interface TestPayArgs { + merchantBaseUrl: string; + merchantAuthToken?: string; + amount: AmountString; + summary: string; + forcedCoinSel?: ForcedCoinSel; +} + +export const codecForTestPayArgs = (): Codec<TestPayArgs> => + buildCodecForObject<TestPayArgs>() + .property("merchantBaseUrl", codecForCanonBaseUrl()) + .property("merchantAuthToken", codecOptional(codecForString())) + .property("amount", codecForAmountString()) + .property("summary", codecForString()) + .property("forcedCoinSel", codecForAny()) + .build("TestPayArgs"); + +export interface IntegrationTestArgs { + exchangeBaseUrl: string; + corebankApiBaseUrl: string; + merchantBaseUrl: string; + merchantAuthToken?: string; + amountToWithdraw: AmountString; + amountToSpend: AmountString; +} + +export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> => + buildCodecForObject<IntegrationTestArgs>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("merchantBaseUrl", codecForCanonBaseUrl()) + .property("merchantAuthToken", codecOptional(codecForString())) + .property("amountToSpend", codecForAmountString()) + .property("amountToWithdraw", codecForAmountString()) + .property("corebankApiBaseUrl", codecForCanonBaseUrl()) + .build("IntegrationTestArgs"); + +export interface IntegrationTestV2Args { + exchangeBaseUrl: string; + corebankApiBaseUrl: string; + merchantBaseUrl: string; + merchantAuthToken?: string; +} + +export const codecForIntegrationTestV2Args = (): Codec<IntegrationTestV2Args> => + buildCodecForObject<IntegrationTestV2Args>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("merchantBaseUrl", codecForCanonBaseUrl()) + .property("merchantAuthToken", codecOptional(codecForString())) + .property("corebankApiBaseUrl", codecForCanonBaseUrl()) + .build("IntegrationTestV2Args"); + +export interface GetExchangeEntryByUrlRequest { + exchangeBaseUrl: string; +} + +export const codecForGetExchangeEntryByUrlRequest = + (): Codec<GetExchangeEntryByUrlRequest> => + buildCodecForObject<GetExchangeEntryByUrlRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .build("GetExchangeEntryByUrlRequest"); + +export type GetExchangeEntryByUrlResponse = ExchangeListItem; + +export interface AddExchangeRequest { + exchangeBaseUrl: string; + + /** + * @deprecated use a separate API call to start a forced exchange update instead + */ + forceUpdate?: boolean; +} + +export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => + buildCodecForObject<AddExchangeRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("forceUpdate", codecOptional(codecForBoolean())) + .build("AddExchangeRequest"); + +export interface UpdateExchangeEntryRequest { + exchangeBaseUrl: string; + force?: boolean; +} + +export const codecForUpdateExchangeEntryRequest = + (): Codec<UpdateExchangeEntryRequest> => + buildCodecForObject<UpdateExchangeEntryRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("force", codecOptional(codecForBoolean())) + .build("UpdateExchangeEntryRequest"); + +export interface GetExchangeResourcesRequest { + exchangeBaseUrl: string; +} + +export const codecForGetExchangeResourcesRequest = + (): Codec<GetExchangeResourcesRequest> => + buildCodecForObject<GetExchangeResourcesRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .build("GetExchangeResourcesRequest"); + +export interface GetExchangeResourcesResponse { + hasResources: boolean; +} + +export interface DeleteExchangeRequest { + exchangeBaseUrl: string; + purge?: boolean; +} + +export const codecForDeleteExchangeRequest = (): Codec<DeleteExchangeRequest> => + buildCodecForObject<DeleteExchangeRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("purge", codecOptional(codecForBoolean())) + .build("DeleteExchangeRequest"); + +export interface ForceExchangeUpdateRequest { + exchangeBaseUrl: string; +} + +export const codecForForceExchangeUpdateRequest = + (): Codec<AddExchangeRequest> => + buildCodecForObject<AddExchangeRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .build("AddExchangeRequest"); + +export interface GetExchangeTosRequest { + exchangeBaseUrl: string; + acceptedFormat?: string[]; + acceptLanguage?: string; +} + +export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> => + buildCodecForObject<GetExchangeTosRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("acceptedFormat", codecOptional(codecForList(codecForString()))) + .property("acceptLanguage", codecOptional(codecForString())) + .build("GetExchangeTosRequest"); + +export interface AcceptManualWithdrawalRequest { + exchangeBaseUrl: string; + amount: AmountString; + restrictAge?: number; + + /** + * Instead of generating a fresh, random reserve key pair, + * use the provided reserve private key. + * + * Use with caution. Usage of this field may be restricted + * to developer mode. + */ + forceReservePriv?: EddsaPrivateKeyString; +} + +export const codecForAcceptManualWithdrawalRequest = + (): Codec<AcceptManualWithdrawalRequest> => + buildCodecForObject<AcceptManualWithdrawalRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("amount", codecForAmountString()) + .property("restrictAge", codecOptional(codecForNumber())) + .property("forceReservePriv", codecOptional(codecForString())) + .build("AcceptManualWithdrawalRequest"); + +export interface GetWithdrawalDetailsForAmountRequest { + exchangeBaseUrl: string; + amount: AmountString; + restrictAge?: number; + + /** + * ID provided by the client to cancel the request. + * + * If the same request is made again with the same clientCancellationId, + * all previous requests are cancelled. + * + * The cancelled request will receive an error response with + * an error code that indicates the cancellation. + * + * The cancellation is best-effort, responses might still arrive. + */ + clientCancellationId?: string; +} + +export interface PrepareBankIntegratedWithdrawalRequest { + talerWithdrawUri: string; +} + +export const codecForPrepareBankIntegratedWithdrawalRequest = + (): Codec<PrepareBankIntegratedWithdrawalRequest> => + buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>() + .property("talerWithdrawUri", codecForString()) + .build("PrepareBankIntegratedWithdrawalRequest"); + +export interface PrepareBankIntegratedWithdrawalResponse { + transactionId: TransactionIdStr; + info: WithdrawUriInfoResponse; +} + +export interface ConfirmWithdrawalRequest { + transactionId: string; + exchangeBaseUrl: string; + amount: AmountString; + forcedDenomSel?: ForcedDenomSel; + restrictAge?: number; +} + +export const codecForConfirmWithdrawalRequestRequest = + (): Codec<ConfirmWithdrawalRequest> => + buildCodecForObject<ConfirmWithdrawalRequest>() + .property("transactionId", codecForString()) + .property("amount", codecForAmountString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("forcedDenomSel", codecForAny()) + .property("restrictAge", codecOptional(codecForNumber())) + .build("ConfirmWithdrawalRequest"); + +export interface AcceptBankIntegratedWithdrawalRequest { + talerWithdrawUri: string; + exchangeBaseUrl: string; + forcedDenomSel?: ForcedDenomSel; + /** + * Amount to withdraw. + * If the bank's withdrawal operation uses a fixed amount, + * this field must either be left undefined or its value must match + * the amount from the withdrawal operation. + */ + amount?: AmountString; + restrictAge?: number; +} + +export const codecForAcceptBankIntegratedWithdrawalRequest = + (): Codec<AcceptBankIntegratedWithdrawalRequest> => + buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("talerWithdrawUri", codecForString()) + .property("forcedDenomSel", codecForAny()) + .property("amount", codecOptional(codecForAmountString())) + .property("restrictAge", codecOptional(codecForNumber())) + .build("AcceptBankIntegratedWithdrawalRequest"); + +export const codecForGetWithdrawalDetailsForAmountRequest = + (): Codec<GetWithdrawalDetailsForAmountRequest> => + buildCodecForObject<GetWithdrawalDetailsForAmountRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("amount", codecForAmountString()) + .property("restrictAge", codecOptional(codecForNumber())) + .property("clientCancellationId", codecOptional(codecForString())) + .build("GetWithdrawalDetailsForAmountRequest"); + +export interface AcceptExchangeTosRequest { + exchangeBaseUrl: string; +} + +export const codecForAcceptExchangeTosRequest = + (): Codec<AcceptExchangeTosRequest> => + buildCodecForObject<AcceptExchangeTosRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .build("AcceptExchangeTosRequest"); + +export interface ForgetExchangeTosRequest { + exchangeBaseUrl: string; +} + +export const codecForForgetExchangeTosRequest = + (): Codec<ForgetExchangeTosRequest> => + buildCodecForObject<ForgetExchangeTosRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .build("ForgetExchangeTosRequest"); + +export interface AcceptRefundRequest { + transactionId: TransactionIdStr; +} + +export const codecForApplyRefundRequest = (): Codec<AcceptRefundRequest> => + buildCodecForObject<AcceptRefundRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("AcceptRefundRequest"); + +export interface ApplyRefundFromPurchaseIdRequest { + purchaseId: string; +} + +export const codecForApplyRefundFromPurchaseIdRequest = + (): Codec<ApplyRefundFromPurchaseIdRequest> => + buildCodecForObject<ApplyRefundFromPurchaseIdRequest>() + .property("purchaseId", codecForString()) + .build("ApplyRefundFromPurchaseIdRequest"); + +export interface GetWithdrawalDetailsForUriRequest { + talerWithdrawUri: string; + /** + * @deprecated not used + */ + restrictAge?: number; +} + +export const codecForGetWithdrawalDetailsForUri = + (): Codec<GetWithdrawalDetailsForUriRequest> => + buildCodecForObject<GetWithdrawalDetailsForUriRequest>() + .property("talerWithdrawUri", codecForString()) + .property("restrictAge", codecOptional(codecForNumber())) + .build("GetWithdrawalDetailsForUriRequest"); + +export interface ListKnownBankAccountsRequest { + currency?: string; +} + +export const codecForListKnownBankAccounts = + (): Codec<ListKnownBankAccountsRequest> => + buildCodecForObject<ListKnownBankAccountsRequest>() + .property("currency", codecOptional(codecForString())) + .build("ListKnownBankAccountsRequest"); + +export interface AddKnownBankAccountsRequest { + payto: string; + alias: string; + currency: string; +} +export const codecForAddKnownBankAccounts = + (): Codec<AddKnownBankAccountsRequest> => + buildCodecForObject<AddKnownBankAccountsRequest>() + .property("payto", codecForString()) + .property("alias", codecForString()) + .property("currency", codecForString()) + .build("AddKnownBankAccountsRequest"); + +export interface ForgetKnownBankAccountsRequest { + payto: string; +} + +export const codecForForgetKnownBankAccounts = + (): Codec<ForgetKnownBankAccountsRequest> => + buildCodecForObject<ForgetKnownBankAccountsRequest>() + .property("payto", codecForString()) + .build("ForgetKnownBankAccountsRequest"); + +export interface AbortProposalRequest { + proposalId: string; +} + +export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> => + buildCodecForObject<AbortProposalRequest>() + .property("proposalId", codecForString()) + .build("AbortProposalRequest"); + +export interface GetContractTermsDetailsRequest { + // @deprecated use transaction id + proposalId?: string; + transactionId?: string; +} + +export const codecForGetContractTermsDetails = + (): Codec<GetContractTermsDetailsRequest> => + buildCodecForObject<GetContractTermsDetailsRequest>() + .property("proposalId", codecOptional(codecForString())) + .property("transactionId", codecOptional(codecForString())) + .build("GetContractTermsDetails"); + +export interface PreparePayRequest { + talerPayUri: string; +} + +export const codecForPreparePayRequest = (): Codec<PreparePayRequest> => + buildCodecForObject<PreparePayRequest>() + .property("talerPayUri", codecForString()) + .build("PreparePay"); + +export interface SharePaymentRequest { + merchantBaseUrl: string; + orderId: string; +} +export const codecForSharePaymentRequest = (): Codec<SharePaymentRequest> => + buildCodecForObject<SharePaymentRequest>() + .property("merchantBaseUrl", codecForCanonBaseUrl()) + .property("orderId", codecForString()) + .build("SharePaymentRequest"); + +export interface SharePaymentResult { + privatePayUri: string; +} +export const codecForSharePaymentResult = (): Codec<SharePaymentResult> => + buildCodecForObject<SharePaymentResult>() + .property("privatePayUri", codecForString()) + .build("SharePaymentResult"); + +export interface CheckPayTemplateRequest { + talerPayTemplateUri: string; +} + +export type CheckPayTemplateReponse = { + templateDetails: TalerMerchantApi.WalletTemplateDetails; + supportedCurrencies: string[]; +}; + +export const codecForCheckPayTemplateRequest = + (): Codec<CheckPayTemplateRequest> => + buildCodecForObject<CheckPayTemplateRequest>() + .property("talerPayTemplateUri", codecForString()) + .build("CheckPayTemplateRequest"); + +export interface PreparePayTemplateRequest { + talerPayTemplateUri: string; + templateParams?: TemplateParams; +} + +export const codecForPreparePayTemplateRequest = + (): Codec<PreparePayTemplateRequest> => + buildCodecForObject<PreparePayTemplateRequest>() + .property("talerPayTemplateUri", codecForString()) + .property("templateParams", codecForAny()) + .build("PreparePayTemplate"); + +export interface ConfirmPayRequest { + /** + * @deprecated use transactionId instead + */ + proposalId?: string; + transactionId?: TransactionIdStr; + sessionId?: string; + forcedCoinSel?: ForcedCoinSel; +} + +export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> => + buildCodecForObject<ConfirmPayRequest>() + .property("proposalId", codecOptional(codecForString())) + .property("transactionId", codecOptional(codecForTransactionIdStr())) + .property("sessionId", codecOptional(codecForString())) + .property("forcedCoinSel", codecForAny()) + .build("ConfirmPay"); + +export interface CoreApiRequestEnvelope { + id: string; + operation: string; + args: unknown; +} + +export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; + +export type CoreApiMessageEnvelope = CoreApiResponse | CoreApiNotification; + +export interface CoreApiNotification { + type: "notification"; + payload: unknown; +} + +export interface CoreApiResponseSuccess { + // To distinguish the message from notifications + type: "response"; + operation: string; + id: string; + result: unknown; +} + +export interface CoreApiResponseError { + // To distinguish the message from notifications + type: "error"; + operation: string; + id: string; + error: TalerErrorDetail; +} + +export interface WithdrawTestBalanceRequest { + amount: AmountString; + /** + * Corebank API base URL. + */ + corebankApiBaseUrl: string; + exchangeBaseUrl: string; + forcedDenomSel?: ForcedDenomSel; +} + +/** + * Request to the crypto worker to make a sync signature. + */ +export interface MakeSyncSignatureRequest { + accountPriv: string; + oldHash: string | undefined; + newHash: string; +} + +/** + * Planchet for a coin during refresh. + */ +export interface RefreshPlanchetInfo { + /** + * Public key for the coin. + */ + coinPub: string; + + /** + * Private key for the coin. + */ + coinPriv: string; + + /** + * Blinded public key. + */ + coinEv: CoinEnvelope; + + coinEvHash: string; + + /** + * Blinding key used. + */ + blindingKey: string; + + maxAge: number; + ageCommitmentProof?: AgeCommitmentProof; +} + +/** + * Strategy for loading recovery information. + */ +export enum RecoveryMergeStrategy { + /** + * Keep the local wallet root key, import and take over providers. + */ + Ours = "ours", + + /** + * Migrate to the wallet root key from the recovery information. + */ + Theirs = "theirs", +} + +/** + * Load recovery information into the wallet. + */ +export interface RecoveryLoadRequest { + recovery: BackupRecovery; + strategy?: RecoveryMergeStrategy; +} + +export const codecForWithdrawTestBalance = + (): Codec<WithdrawTestBalanceRequest> => + buildCodecForObject<WithdrawTestBalanceRequest>() + .property("amount", codecForAmountString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("forcedDenomSel", codecForAny()) + .property("corebankApiBaseUrl", codecForCanonBaseUrl()) + .build("WithdrawTestBalanceRequest"); + +export interface SetCoinSuspendedRequest { + coinPub: string; + suspended: boolean; +} + +export const codecForSetCoinSuspendedRequest = + (): Codec<SetCoinSuspendedRequest> => + buildCodecForObject<SetCoinSuspendedRequest>() + .property("coinPub", codecForString()) + .property("suspended", codecForBoolean()) + .build("SetCoinSuspendedRequest"); + +export interface RefreshCoinSpec { + coinPub: string; + amount?: AmountString; +} + +export const codecForRefreshCoinSpec = (): Codec<RefreshCoinSpec> => + buildCodecForObject<RefreshCoinSpec>() + .property("amount", codecForAmountString()) + .property("coinPub", codecForString()) + .build("ForceRefreshRequest"); + +export interface ForceRefreshRequest { + refreshCoinSpecs: RefreshCoinSpec[]; +} + +export const codecForForceRefreshRequest = (): Codec<ForceRefreshRequest> => + buildCodecForObject<ForceRefreshRequest>() + .property("refreshCoinSpecs", codecForList(codecForRefreshCoinSpec())) + .build("ForceRefreshRequest"); + +export interface PrepareRefundRequest { + talerRefundUri: string; +} + +export interface StartRefundQueryForUriResponse { + /** + * Transaction id of the *payment* where the refund query was started. + */ + transactionId: TransactionIdStr; +} + +export const codecForPrepareRefundRequest = (): Codec<PrepareRefundRequest> => + buildCodecForObject<PrepareRefundRequest>() + .property("talerRefundUri", codecForString()) + .build("PrepareRefundRequest"); + +export interface StartRefundQueryRequest { + transactionId: TransactionIdStr; +} + +export const codecForStartRefundQueryRequest = + (): Codec<StartRefundQueryRequest> => + buildCodecForObject<StartRefundQueryRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("StartRefundQueryRequest"); + +export interface FailTransactionRequest { + transactionId: TransactionIdStr; +} + +export const codecForFailTransactionRequest = + (): Codec<FailTransactionRequest> => + buildCodecForObject<FailTransactionRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("FailTransactionRequest"); + +export interface SuspendTransactionRequest { + transactionId: TransactionIdStr; +} + +export const codecForSuspendTransaction = + (): Codec<SuspendTransactionRequest> => + buildCodecForObject<AbortTransactionRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("SuspendTransactionRequest"); + +export interface ResumeTransactionRequest { + transactionId: TransactionIdStr; +} + +export const codecForResumeTransaction = (): Codec<ResumeTransactionRequest> => + buildCodecForObject<ResumeTransactionRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("ResumeTransactionRequest"); + +export interface AbortTransactionRequest { + transactionId: TransactionIdStr; +} + +export interface FailTransactionRequest { + transactionId: TransactionIdStr; +} + +export const codecForAbortTransaction = (): Codec<AbortTransactionRequest> => + buildCodecForObject<AbortTransactionRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("AbortTransactionRequest"); + +export interface DepositGroupFees { + coin: AmountString; + wire: AmountString; + refresh: AmountString; +} + +export interface CreateDepositGroupRequest { + /** + * Pre-allocated transaction ID. + * Allows clients to easily handle notifications + * that occur while the operation has been created but + * before the creation request has returned. + */ + transactionId?: TransactionIdStr; + depositPaytoUri: string; + amount: AmountString; +} + +export interface PrepareDepositRequest { + depositPaytoUri: string; + amount: AmountString; +} +export const codecForPrepareDepositRequest = (): Codec<PrepareDepositRequest> => + buildCodecForObject<PrepareDepositRequest>() + .property("amount", codecForAmountString()) + .property("depositPaytoUri", codecForString()) + .build("PrepareDepositRequest"); + +export interface PrepareDepositResponse { + totalDepositCost: AmountString; + effectiveDepositAmount: AmountString; + fees: DepositGroupFees; +} + +export const codecForCreateDepositGroupRequest = + (): Codec<CreateDepositGroupRequest> => + buildCodecForObject<CreateDepositGroupRequest>() + .property("amount", codecForAmountString()) + .property("depositPaytoUri", codecForString()) + .property("transactionId", codecOptional(codecForTransactionIdStr())) + .build("CreateDepositGroupRequest"); + +export interface CreateDepositGroupResponse { + depositGroupId: string; + transactionId: TransactionIdStr; +} + +export interface TxIdResponse { + transactionId: TransactionIdStr; +} + +export interface WithdrawUriInfoResponse { + operationId: string; + status: WithdrawalOperationStatus; + confirmTransferUrl?: string; + currency: string; + amount: AmountString | undefined; + editableAmount: boolean; + maxAmount: AmountString | undefined; + wireFee: AmountString | undefined; + defaultExchangeBaseUrl?: string; + editableExchange: boolean; + possibleExchanges: ExchangeListItem[]; +} + +export const codecForWithdrawUriInfoResponse = + (): Codec<WithdrawUriInfoResponse> => + buildCodecForObject<WithdrawUriInfoResponse>() + .property("operationId", codecForString()) + .property("confirmTransferUrl", codecOptional(codecForString())) + .property( + "status", + codecForEither( + codecForConstString("pending"), + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) + .property("amount", codecOptional(codecForAmountString())) + .property("maxAmount", codecOptional(codecForAmountString())) + .property("wireFee", codecOptional(codecForAmountString())) + .property("currency", codecForString()) + .property("editableAmount", codecForBoolean()) + .property("editableExchange", codecForBoolean()) + .property("defaultExchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) + .property("possibleExchanges", codecForList(codecForExchangeListItem())) + .build("WithdrawUriInfoResponse"); + +export interface WalletCurrencyInfo { + trustedAuditors: { + currency: string; + auditorPub: string; + auditorBaseUrl: string; + }[]; + trustedExchanges: { + currency: string; + exchangeMasterPub: string; + exchangeBaseUrl: string; + }[]; +} + +export interface TestingListTasksForTransactionRequest { + transactionId: TransactionIdStr; +} + +export interface TestingListTasksForTransactionsResponse { + taskIdList: string[]; +} + +export const codecForTestingListTasksForTransactionRequest = + (): Codec<TestingListTasksForTransactionRequest> => + buildCodecForObject<TestingListTasksForTransactionRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("TestingListTasksForTransactionRequest"); + +export interface DeleteTransactionRequest { + transactionId: TransactionIdStr; +} + +export interface RetryTransactionRequest { + transactionId: TransactionIdStr; +} + +export const codecForDeleteTransactionRequest = + (): Codec<DeleteTransactionRequest> => + buildCodecForObject<DeleteTransactionRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("DeleteTransactionRequest"); + +export const codecForRetryTransactionRequest = + (): Codec<RetryTransactionRequest> => + buildCodecForObject<RetryTransactionRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("RetryTransactionRequest"); + +export interface SetWalletDeviceIdRequest { + /** + * New wallet device ID to set. + */ + walletDeviceId: string; +} + +export const codecForSetWalletDeviceIdRequest = + (): Codec<SetWalletDeviceIdRequest> => + buildCodecForObject<SetWalletDeviceIdRequest>() + .property("walletDeviceId", codecForString()) + .build("SetWalletDeviceIdRequest"); + +export interface WithdrawFakebankRequest { + amount: AmountString; + exchange: string; + bank: string; +} + +export enum AttentionPriority { + High = "high", + Medium = "medium", + Low = "low", +} + +export interface UserAttentionByIdRequest { + entityId: string; + type: AttentionType; +} + +export const codecForUserAttentionByIdRequest = + (): Codec<UserAttentionByIdRequest> => + buildCodecForObject<UserAttentionByIdRequest>() + .property("type", codecForAny()) + .property("entityId", codecForString()) + .build("UserAttentionByIdRequest"); + +export const codecForUserAttentionsRequest = (): Codec<UserAttentionsRequest> => + buildCodecForObject<UserAttentionsRequest>() + .property( + "priority", + codecOptional( + codecForEither( + codecForConstString(AttentionPriority.Low), + codecForConstString(AttentionPriority.Medium), + codecForConstString(AttentionPriority.High), + ), + ), + ) + .build("UserAttentionsRequest"); + +export interface UserAttentionsRequest { + priority?: AttentionPriority; +} + +export type AttentionInfo = + | AttentionKycWithdrawal + | AttentionBackupUnpaid + | AttentionBackupExpiresSoon + | AttentionMerchantRefund + | AttentionExchangeTosChanged + | AttentionExchangeKeyExpired + | AttentionExchangeDenominationExpired + | AttentionAuditorTosChanged + | AttentionAuditorKeyExpires + | AttentionAuditorDenominationExpires + | AttentionPullPaymentPaid + | AttentionPushPaymentReceived; + +export enum AttentionType { + KycWithdrawal = "kyc-withdrawal", + + BackupUnpaid = "backup-unpaid", + BackupExpiresSoon = "backup-expires-soon", + MerchantRefund = "merchant-refund", + + ExchangeTosChanged = "exchange-tos-changed", + ExchangeKeyExpired = "exchange-key-expired", + ExchangeKeyExpiresSoon = "exchange-key-expires-soon", + ExchangeDenominationsExpired = "exchange-denominations-expired", + ExchangeDenominationsExpiresSoon = "exchange-denominations-expires-soon", + + AuditorTosChanged = "auditor-tos-changed", + AuditorKeyExpires = "auditor-key-expires", + AuditorDenominationsExpires = "auditor-denominations-expires", + + PullPaymentPaid = "pull-payment-paid", + PushPaymentReceived = "push-payment-withdrawn", +} + +export const UserAttentionPriority: { + [type in AttentionType]: AttentionPriority; +} = { + "kyc-withdrawal": AttentionPriority.Medium, + + "backup-unpaid": AttentionPriority.High, + "backup-expires-soon": AttentionPriority.Medium, + "merchant-refund": AttentionPriority.Medium, + + "exchange-tos-changed": AttentionPriority.Medium, + + "exchange-key-expired": AttentionPriority.High, + "exchange-key-expires-soon": AttentionPriority.Medium, + "exchange-denominations-expired": AttentionPriority.High, + "exchange-denominations-expires-soon": AttentionPriority.Medium, + + "auditor-tos-changed": AttentionPriority.Medium, + "auditor-key-expires": AttentionPriority.Medium, + "auditor-denominations-expires": AttentionPriority.Medium, + + "pull-payment-paid": AttentionPriority.High, + "push-payment-withdrawn": AttentionPriority.High, +}; + +interface AttentionBackupExpiresSoon { + type: AttentionType.BackupExpiresSoon; + provider_base_url: string; +} +interface AttentionBackupUnpaid { + type: AttentionType.BackupUnpaid; + provider_base_url: string; + talerUri: string; +} + +interface AttentionMerchantRefund { + type: AttentionType.MerchantRefund; + transactionId: TransactionIdStr; +} + +interface AttentionKycWithdrawal { + type: AttentionType.KycWithdrawal; + transactionId: TransactionIdStr; +} + +interface AttentionExchangeTosChanged { + type: AttentionType.ExchangeTosChanged; + exchange_base_url: string; +} +interface AttentionExchangeKeyExpired { + type: AttentionType.ExchangeKeyExpired; + exchange_base_url: string; +} +interface AttentionExchangeDenominationExpired { + type: AttentionType.ExchangeDenominationsExpired; + exchange_base_url: string; +} +interface AttentionAuditorTosChanged { + type: AttentionType.AuditorTosChanged; + auditor_base_url: string; +} + +interface AttentionAuditorKeyExpires { + type: AttentionType.AuditorKeyExpires; + auditor_base_url: string; +} +interface AttentionAuditorDenominationExpires { + type: AttentionType.AuditorDenominationsExpires; + auditor_base_url: string; +} +interface AttentionPullPaymentPaid { + type: AttentionType.PullPaymentPaid; + transactionId: TransactionIdStr; +} + +interface AttentionPushPaymentReceived { + type: AttentionType.PushPaymentReceived; + transactionId: TransactionIdStr; +} + +export type UserAttentionUnreadList = Array<{ + info: AttentionInfo; + when: TalerPreciseTimestamp; + read: boolean; +}>; + +export interface UserAttentionsResponse { + pending: UserAttentionUnreadList; +} + +export interface UserAttentionsCountResponse { + total: number; +} + +export const codecForWithdrawFakebankRequest = + (): Codec<WithdrawFakebankRequest> => + buildCodecForObject<WithdrawFakebankRequest>() + .property("amount", codecForAmountString()) + .property("bank", codecForString()) + .property("exchange", codecForString()) + .build("WithdrawFakebankRequest"); + +export interface ActiveTask { + taskId: string; + transaction: TransactionIdStr | undefined; + firstTry: AbsoluteTime | undefined; + nextTry: AbsoluteTime | undefined; + retryCounter: number | undefined; + lastError: TalerErrorDetail | undefined; +} + +export interface GetActiveTasksResponse { + tasks: ActiveTask[]; +} + +export const codecForActiveTask = (): Codec<ActiveTask> => + buildCodecForObject<ActiveTask>() + .property("taskId", codecForString()) + .property("transaction", codecOptional(codecForTransactionIdStr())) + .property("retryCounter", codecOptional(codecForNumber())) + .property("firstTry", codecOptional(codecForAbsoluteTime)) + .property("nextTry", codecOptional(codecForAbsoluteTime)) + .property("lastError", codecOptional(codecForTalerErrorDetail())) + .build("ActiveTask"); + +export const codecForGetActiveTasks = (): Codec<GetActiveTasksResponse> => + buildCodecForObject<GetActiveTasksResponse>() + .property("tasks", codecForList(codecForActiveTask())) + .build("GetActiveTasks"); + +export interface ImportDbRequest { + dump: any; +} + +export const codecForImportDbRequest = (): Codec<ImportDbRequest> => + buildCodecForObject<ImportDbRequest>() + .property("dump", codecForAny()) + .build("ImportDbRequest"); + +export interface ForcedDenomSel { + denoms: { + value: AmountString; + count: number; + }[]; +} + +/** + * Forced coin selection for deposits/payments. + */ +export interface ForcedCoinSel { + coins: { + value: AmountString; + contribution: AmountString; + }[]; +} + +export interface TestPayResult { + /** + * Number of coins used for the payment. + */ + numCoins: number; +} + +export interface SelectedCoin { + denomPubHash: string; + coinPub: string; + contribution: AmountString; + exchangeBaseUrl: string; +} + +export interface SelectedProspectiveCoin { + denomPubHash: string; + contribution: AmountString; + exchangeBaseUrl: string; +} + +/** + * Result of selecting coins, contains the exchange, and selected + * coins with their denomination. + */ +export interface PayCoinSelection { + coins: SelectedCoin[]; + + /** + * How much of the wire fees is the customer paying? + */ + customerWireFees: AmountString; + + /** + * How much of the deposit fees is the customer paying? + */ + customerDepositFees: AmountString; +} + +export interface ProspectivePayCoinSelection { + prospectiveCoins: SelectedProspectiveCoin[]; + + /** + * How much of the wire fees is the customer paying? + */ + customerWireFees: AmountString; + + /** + * How much of the deposit fees is the customer paying? + */ + customerDepositFees: AmountString; +} + +export interface CheckPeerPushDebitRequest { + /** + * Preferred exchange to use for the p2p payment. + */ + exchangeBaseUrl?: string; + + /** + * Instructed amount. + * + * FIXME: Allow specifying the instructed amount type. + */ + amount: AmountString; +} + +export const codecForCheckPeerPushDebitRequest = + (): Codec<CheckPeerPushDebitRequest> => + buildCodecForObject<CheckPeerPushDebitRequest>() + .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) + .property("amount", codecForAmountString()) + .build("CheckPeerPushDebitRequest"); + +export interface CheckPeerPushDebitResponse { + amountRaw: AmountString; + amountEffective: AmountString; + exchangeBaseUrl: string; + /** + * Maximum expiration date, based on how close the coins + * used for the payment are to expiry. + * + * The value is based on when the wallet would typically + * automatically refresh the coins on its own, leaving enough + * time to get a refund for the push payment and refresh the + * coin. + */ + maxExpirationDate: TalerProtocolTimestamp; +} + +export interface InitiatePeerPushDebitRequest { + exchangeBaseUrl?: string; + partialContractTerms: PeerContractTerms; +} + +export interface InitiatePeerPushDebitResponse { + exchangeBaseUrl: string; + pursePub: string; + mergePriv: string; + contractPriv: string; + transactionId: TransactionIdStr; +} + +export const codecForInitiatePeerPushDebitRequest = + (): Codec<InitiatePeerPushDebitRequest> => + buildCodecForObject<InitiatePeerPushDebitRequest>() + .property("partialContractTerms", codecForPeerContractTerms()) + .build("InitiatePeerPushDebitRequest"); + +export interface PreparePeerPushCreditRequest { + talerUri: string; +} + +export interface PreparePeerPullDebitRequest { + talerUri: string; +} + +export interface PreparePeerPushCreditResponse { + contractTerms: PeerContractTerms; + amountRaw: AmountString; + amountEffective: AmountString; + + transactionId: TransactionIdStr; + + exchangeBaseUrl: string; + + /** + * @deprecated use transaction ID instead. + */ + peerPushCreditId: string; + + /** + * @deprecated + */ + amount: AmountString; +} + +export interface PreparePeerPullDebitResponse { + contractTerms: PeerContractTerms; + /** + * @deprecated Redundant field with bad name, will be removed soon. + */ + amount: AmountString; + + amountRaw: AmountString; + amountEffective: AmountString; + + peerPullDebitId: string; + + transactionId: TransactionIdStr; +} + +export const codecForPreparePeerPushCreditRequest = + (): Codec<PreparePeerPushCreditRequest> => + buildCodecForObject<PreparePeerPushCreditRequest>() + .property("talerUri", codecForString()) + .build("CheckPeerPushPaymentRequest"); + +export const codecForCheckPeerPullPaymentRequest = + (): Codec<PreparePeerPullDebitRequest> => + buildCodecForObject<PreparePeerPullDebitRequest>() + .property("talerUri", codecForString()) + .build("PreparePeerPullDebitRequest"); + +export interface ConfirmPeerPushCreditRequest { + transactionId: string; +} +export interface AcceptPeerPushPaymentResponse { + transactionId: TransactionIdStr; +} + +export interface AcceptPeerPullPaymentResponse { + transactionId: TransactionIdStr; +} + +export const codecForConfirmPeerPushPaymentRequest = + (): Codec<ConfirmPeerPushCreditRequest> => + buildCodecForObject<ConfirmPeerPushCreditRequest>() + .property("transactionId", codecForString()) + .build("ConfirmPeerPushCreditRequest"); + +export interface ConfirmPeerPullDebitRequest { + transactionId: TransactionIdStr; +} + +export interface ApplyDevExperimentRequest { + devExperimentUri: string; +} + +export const codecForApplyDevExperiment = + (): Codec<ApplyDevExperimentRequest> => + buildCodecForObject<ApplyDevExperimentRequest>() + .property("devExperimentUri", codecForString()) + .build("ApplyDevExperimentRequest"); + +export const codecForAcceptPeerPullPaymentRequest = + (): Codec<ConfirmPeerPullDebitRequest> => + buildCodecForObject<ConfirmPeerPullDebitRequest>() + .property("transactionId", codecForTransactionIdStr()) + .build("ConfirmPeerPullDebitRequest"); + +export interface CheckPeerPullCreditRequest { + exchangeBaseUrl?: string; + amount: AmountString; +} +export const codecForPreparePeerPullPaymentRequest = + (): Codec<CheckPeerPullCreditRequest> => + buildCodecForObject<CheckPeerPullCreditRequest>() + .property("amount", codecForAmountString()) + .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) + .build("CheckPeerPullCreditRequest"); + +export interface CheckPeerPullCreditResponse { + exchangeBaseUrl: string; + amountRaw: AmountString; + amountEffective: AmountString; + + /** + * Number of coins that will be used, + * can be used by the UI to warn if excessively large. + */ + numCoins: number; +} +export interface InitiatePeerPullCreditRequest { + exchangeBaseUrl?: string; + partialContractTerms: PeerContractTerms; +} + +export const codecForInitiatePeerPullPaymentRequest = + (): Codec<InitiatePeerPullCreditRequest> => + buildCodecForObject<InitiatePeerPullCreditRequest>() + .property("partialContractTerms", codecForPeerContractTerms()) + .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) + .build("InitiatePeerPullCreditRequest"); + +export interface InitiatePeerPullCreditResponse { + /** + * Taler URI for the other party to make the payment + * that was requested. + * + * @deprecated since it's not necessarily valid yet until the tx is in the right state + */ + talerUri: string; + + transactionId: TransactionIdStr; +} + +export interface CanonicalizeBaseUrlRequest { + url: string; +} + +export const codecForCanonicalizeBaseUrlRequest = + (): Codec<CanonicalizeBaseUrlRequest> => + buildCodecForObject<CanonicalizeBaseUrlRequest>() + .property("url", codecForString()) + .build("CanonicalizeBaseUrlRequest"); + +export interface CanonicalizeBaseUrlResponse { + url: string; +} + +export interface ValidateIbanRequest { + iban: string; +} + +export const codecForValidateIbanRequest = (): Codec<ValidateIbanRequest> => + buildCodecForObject<ValidateIbanRequest>() + .property("iban", codecForString()) + .build("ValidateIbanRequest"); + +export interface ValidateIbanResponse { + valid: boolean; +} + +export const codecForValidateIbanResponse = (): Codec<ValidateIbanResponse> => + buildCodecForObject<ValidateIbanResponse>() + .property("valid", codecForBoolean()) + .build("ValidateIbanResponse"); + +export type TransactionStateFilter = "nonfinal"; + +export interface TransactionRecordFilter { + onlyState?: TransactionStateFilter; + onlyCurrency?: string; +} + +export interface StoredBackupList { + storedBackups: { + name: string; + }[]; +} + +export interface CreateStoredBackupResponse { + name: string; +} + +export interface RecoverStoredBackupRequest { + name: string; +} + +export interface DeleteStoredBackupRequest { + name: string; +} + +export const codecForDeleteStoredBackupRequest = + (): Codec<DeleteStoredBackupRequest> => + buildCodecForObject<DeleteStoredBackupRequest>() + .property("name", codecForString()) + .build("DeleteStoredBackupRequest"); + +export const codecForRecoverStoredBackupRequest = + (): Codec<RecoverStoredBackupRequest> => + buildCodecForObject<RecoverStoredBackupRequest>() + .property("name", codecForString()) + .build("RecoverStoredBackupRequest"); + +export interface TestingSetTimetravelRequest { + offsetMs: number; +} + +export const codecForTestingSetTimetravelRequest = + (): Codec<TestingSetTimetravelRequest> => + buildCodecForObject<TestingSetTimetravelRequest>() + .property("offsetMs", codecForNumber()) + .build("TestingSetTimetravelRequest"); + +export interface AllowedAuditorInfo { + auditorBaseUrl: string; + auditorPub: string; +} + +export interface AllowedExchangeInfo { + exchangeBaseUrl: string; + exchangePub: string; +} + +/** + * Data extracted from the contract terms that is relevant for payment + * processing in the wallet. + */ +export interface WalletContractData { + /** + * Fulfillment URL, or the empty string if the order has no fulfillment URL. + * + * Stored as a non-nullable string as we use this field for IndexedDB indexing. + */ + fulfillmentUrl: string; + + contractTermsHash: string; + fulfillmentMessage?: string; + fulfillmentMessageI18n?: InternationalizedString; + merchantSig: string; + merchantPub: string; + merchant: MerchantInfo; + amount: AmountString; + orderId: string; + merchantBaseUrl: string; + summary: string; + summaryI18n: { [lang_tag: string]: string } | undefined; + autoRefund: TalerProtocolDuration | undefined; + payDeadline: TalerProtocolTimestamp; + refundDeadline: TalerProtocolTimestamp; + allowedExchanges: AllowedExchangeInfo[]; + timestamp: TalerProtocolTimestamp; + wireMethod: string; + wireInfoHash: string; + maxDepositFee: AmountString; + minimumAge?: number; +} + +export interface TestingWaitTransactionRequest { + transactionId: TransactionIdStr; + txState: TransactionState; +} + +export interface TestingGetReserveHistoryRequest { + reservePub: string; + exchangeBaseUrl: string; +} + +export const codecForTestingGetReserveHistoryRequest = + (): Codec<TestingGetReserveHistoryRequest> => + buildCodecForObject<TestingGetReserveHistoryRequest>() + .property("reservePub", codecForString()) + .property("exchangeBaseUrl", codecForString()) + .build("TestingGetReserveHistoryRequest"); + +export interface TestingGetDenomStatsRequest { + exchangeBaseUrl: string; +} + +export interface TestingGetDenomStatsResponse { + numKnown: number; + numOffered: number; + numLost: number; +} + +export const codecForTestingGetDenomStatsRequest = + (): Codec<TestingGetDenomStatsRequest> => + buildCodecForObject<TestingGetDenomStatsRequest>() + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .build("TestingGetDenomStatsRequest"); + +export interface WithdrawalExchangeAccountDetails { + /** + * Payto URI to credit the exchange. + * + * Depending on whether the (manual!) withdrawal is accepted or just + * being checked, this already includes the subject with the + * reserve public key. + */ + paytoUri: string; + + /** + * Status that indicates whether the account can be used + * by the user to send funds for a withdrawal. + * + * ok: account should be shown to the user + * error: account should not be shown to the user, UIs might render the error (in conversionError), + * especially in dev mode. + */ + status: "ok" | "error"; + + /** + * Transfer amount. Might be in a different currency than the requested + * amount for withdrawal. + * + * Absent if this is a conversion account and the conversion failed. + */ + transferAmount?: AmountString; + + /** + * Currency specification for the external currency. + * + * Only included if this account requires a currency conversion. + */ + currencySpecification?: CurrencySpecification; + + /** + * Further restrictions for sending money to the + * exchange. + */ + creditRestrictions?: AccountRestriction[]; + + /** + * Label given to the account or the account's bank by the exchange. + */ + bankLabel?: string; + + /* + * Display priority assigned to this bank account by the exchange. + */ + priority?: number; + + /** + * Error that happened when attempting to request the conversion rate. + */ + conversionError?: TalerErrorDetail; +} + +export interface PrepareWithdrawExchangeRequest { + /** + * A taler://withdraw-exchange URI. + */ + talerUri: string; +} + +export const codecForPrepareWithdrawExchangeRequest = + (): Codec<PrepareWithdrawExchangeRequest> => + buildCodecForObject<PrepareWithdrawExchangeRequest>() + .property("talerUri", codecForString()) + .build("PrepareWithdrawExchangeRequest"); + +export interface PrepareWithdrawExchangeResponse { + /** + * Base URL of the exchange that already existed + * or was ephemerally added as an exchange entry to + * the wallet. + */ + exchangeBaseUrl: string; + + /** + * Amount from the taler://withdraw-exchange URI. + * Only present if specified in the URI. + */ + amount?: AmountString; +} + +export interface ExchangeEntryState { + tosStatus: ExchangeTosStatus; + exchangeEntryStatus: ExchangeEntryStatus; + exchangeUpdateStatus: ExchangeUpdateStatus; +} + +export interface ListGlobalCurrencyAuditorsResponse { + auditors: { + currency: string; + auditorBaseUrl: string; + auditorPub: string; + }[]; +} + +export interface ListGlobalCurrencyExchangesResponse { + exchanges: { + currency: string; + exchangeBaseUrl: string; + exchangeMasterPub: string; + }[]; +} + +export interface AddGlobalCurrencyExchangeRequest { + currency: string; + exchangeBaseUrl: string; + exchangeMasterPub: string; +} + +export const codecForAddGlobalCurrencyExchangeRequest = + (): Codec<AddGlobalCurrencyExchangeRequest> => + buildCodecForObject<AddGlobalCurrencyExchangeRequest>() + .property("currency", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("exchangeMasterPub", codecForString()) + .build("AddGlobalCurrencyExchangeRequest"); + +export interface RemoveGlobalCurrencyExchangeRequest { + currency: string; + exchangeBaseUrl: string; + exchangeMasterPub: string; +} + +export const codecForRemoveGlobalCurrencyExchangeRequest = + (): Codec<RemoveGlobalCurrencyExchangeRequest> => + buildCodecForObject<RemoveGlobalCurrencyExchangeRequest>() + .property("currency", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("exchangeMasterPub", codecForString()) + .build("RemoveGlobalCurrencyExchangeRequest"); + +export interface AddGlobalCurrencyAuditorRequest { + currency: string; + auditorBaseUrl: string; + auditorPub: string; +} + +export const codecForAddGlobalCurrencyAuditorRequest = + (): Codec<AddGlobalCurrencyAuditorRequest> => + buildCodecForObject<AddGlobalCurrencyAuditorRequest>() + .property("currency", codecForString()) + .property("auditorBaseUrl", codecForCanonBaseUrl()) + .property("auditorPub", codecForString()) + .build("AddGlobalCurrencyAuditorRequest"); + +export interface RemoveGlobalCurrencyAuditorRequest { + currency: string; + auditorBaseUrl: string; + auditorPub: string; +} + +export const codecForRemoveGlobalCurrencyAuditorRequest = + (): Codec<RemoveGlobalCurrencyAuditorRequest> => + buildCodecForObject<RemoveGlobalCurrencyAuditorRequest>() + .property("currency", codecForString()) + .property("auditorBaseUrl", codecForCanonBaseUrl()) + .property("auditorPub", codecForString()) + .build("RemoveGlobalCurrencyAuditorRequest"); + +/** + * Information about one provider. + * + * We don't store the account key here, + * as that's derived from the wallet root key. + */ +export interface ProviderInfo { + active: boolean; + syncProviderBaseUrl: string; + name: string; + terms?: BackupProviderTerms; + /** + * Last communication issue with the provider. + */ + lastError?: TalerErrorDetail; + lastSuccessfulBackupTimestamp?: TalerPreciseTimestamp; + lastAttemptedBackupTimestamp?: TalerPreciseTimestamp; + paymentProposalIds: string[]; + backupProblem?: BackupProblem; + paymentStatus: ProviderPaymentStatus; +} + +export interface BackupProviderTerms { + supportedProtocolVersion: string; + annualFee: AmountString; + storageLimitInMegabytes: number; +} + +export type BackupProblem = + | BackupUnreadableProblem + | BackupConflictingDeviceProblem; + +export interface BackupUnreadableProblem { + type: "backup-unreadable"; +} + +export interface BackupConflictingDeviceProblem { + type: "backup-conflicting-device"; + otherDeviceId: string; + myDeviceId: string; + backupTimestamp: AbsoluteTime; +} + +export type ProviderPaymentStatus = + | ProviderPaymentTermsChanged + | ProviderPaymentPaid + | ProviderPaymentInsufficientBalance + | ProviderPaymentUnpaid + | ProviderPaymentPending; + +export enum ProviderPaymentType { + Unpaid = "unpaid", + Pending = "pending", + InsufficientBalance = "insufficient-balance", + Paid = "paid", + TermsChanged = "terms-changed", +} + +export interface ProviderPaymentUnpaid { + type: ProviderPaymentType.Unpaid; +} + +export interface ProviderPaymentInsufficientBalance { + type: ProviderPaymentType.InsufficientBalance; + amount: AmountString; +} + +export interface ProviderPaymentPending { + type: ProviderPaymentType.Pending; + talerUri?: string; +} + +export interface ProviderPaymentPaid { + type: ProviderPaymentType.Paid; + paidUntil: AbsoluteTime; +} + +export interface ProviderPaymentTermsChanged { + type: ProviderPaymentType.TermsChanged; + paidUntil: AbsoluteTime; + oldTerms: BackupProviderTerms; + newTerms: BackupProviderTerms; +} + +// FIXME: Does not really belong here, move to sync API +export interface SyncTermsOfServiceResponse { + // maximum backup size supported + storage_limit_in_megabytes: number; + + // Fee for an account, per year. + annual_fee: AmountString; + + // protocol version supported by the server, + // for now always "0.0". + version: string; +} + +// FIXME: Does not really belong here, move to sync API +export const codecForSyncTermsOfServiceResponse = + (): Codec<SyncTermsOfServiceResponse> => + buildCodecForObject<SyncTermsOfServiceResponse>() + .property("storage_limit_in_megabytes", codecForNumber()) + .property("annual_fee", codecForAmountString()) + .property("version", codecForString()) + .build("SyncTermsOfServiceResponse"); + +export interface HintNetworkAvailabilityRequest { + isNetworkAvailable: boolean; +} + +export const codecForHintNetworkAvailabilityRequest = + (): Codec<HintNetworkAvailabilityRequest> => + buildCodecForObject<HintNetworkAvailabilityRequest>() + .property("isNetworkAvailable", codecForBoolean()) + .build("HintNetworkAvailabilityRequest"); + +export interface GetDepositWireTypesForCurrencyRequest { + currency: string; + /** + * Optional scope info to further restrict the result. + * Currency must match the currency field. + */ + scopeInfo?: ScopeInfo; +} + +export const codecForGetDepositWireTypesForCurrencyRequest = + (): Codec<GetDepositWireTypesForCurrencyRequest> => + buildCodecForObject<GetDepositWireTypesForCurrencyRequest>() + .property("currency", codecForString()) + .property("scopeInfo", codecOptional(codecForScopeInfo())) + .build("GetDepositWireTypesForCurrencyRequest"); + +/** + * Response with wire types that are supported for a deposit. + * + * In the future, we might surface more information here, such as debit restrictions + * by the exchange, which then can be shown by UIs to the user before they + * enter their payment information. + */ +export interface GetDepositWireTypesForCurrencyResponse { + wireTypes: string[]; +} + +export interface GetQrCodesForPaytoRequest { + paytoUri: string; +} + +export const codecForGetQrCodesForPaytoRequest = () => + buildCodecForObject<GetQrCodesForPaytoRequest>() + .property("paytoUri", codecForString()) + .build("GetQrCodesForPaytoRequest"); + +export interface GetQrCodesForPaytoResponse { + codes: QrCodeSpec[]; +} + +export type EmptyObject = Record<string, never>; + +export const codecForEmptyObject = (): Codec<EmptyObject> => + buildCodecForObject<EmptyObject>().build("EmptyObject"); diff --git a/packages/taler-util/src/types-taler-wire-gateway.ts b/packages/taler-util/src/types-taler-wire-gateway.ts @@ -0,0 +1,278 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + Codec, + TalerWireGatewayApi, + buildCodecForObject, + buildCodecForUnion, + codecForAmountString, + codecForConstString, + codecForList, + codecForNumber, + codecForString, +} from "./index.js"; +import { PaytoString, codecForPaytoString } from "./payto.js"; +import { codecForTimestamp } from "./time.js"; +import { + AmountString, + EddsaPublicKey, + HashCode, + SafeUint64, + ShortHashCode, + Timestamp, + WadId, +} from "./types-taler-common.js"; + +export interface TransferResponse { + // Timestamp that indicates when the wire transfer will be executed. + // In cases where the wire transfer gateway is unable to know when + // the wire transfer will be executed, the time at which the request + // has been received and stored will be returned. + // The purpose of this field is for debugging (humans trying to find + // the transaction) as well as for taxation (determining which + // time period a transaction belongs to). + timestamp: Timestamp; + + // Opaque ID of the transaction that the bank has made. + row_id: SafeUint64; +} + +export interface TransferRequest { + // Nonce to make the request idempotent. Requests with the same + // transaction_uid that differ in any of the other fields + // are rejected. + request_uid: HashCode; + + // Amount to transfer. + amount: AmountString; + + // Base URL of the exchange. Shall be included by the bank gateway + // in the appropriate section of the wire transfer details. + exchange_base_url: string; + + // Wire transfer identifier chosen by the exchange, + // used by the merchant to identify the Taler order(s) + // associated with this wire transfer. + wtid: ShortHashCode; + + // The recipient's account identifier as a payto URI. + credit_account: PaytoString; +} + +export interface IncomingHistory { + // Array of incoming transactions. + incoming_transactions: IncomingBankTransaction[]; + + // Payto URI to identify the receiver of funds. + // This must be one of the exchange's bank accounts. + // Credit account is shared by all incoming transactions + // as per the nature of the request. + + // undefined if incoming transaction is empty + credit_account?: PaytoString; +} + +// Union discriminated by the "type" field. +export type IncomingBankTransaction = + | IncomingReserveTransaction + | IncomingWadTransaction; + +export interface IncomingReserveTransaction { + type: "RESERVE"; + + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: AmountString; + + // Payto URI to identify the sender of funds. + debit_account: PaytoString; + + // The reserve public key extracted from the transaction details. + reserve_pub: EddsaPublicKey; +} + +export interface IncomingWadTransaction { + type: "WAD"; + + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: AmountString; + + // Payto URI to identify the receiver of funds. + // This must be one of the exchange's bank accounts. + credit_account: PaytoString; + + // Payto URI to identify the sender of funds. + debit_account: PaytoString; + + // Base URL of the exchange that originated the wad. + origin_exchange_url: string; + + // The reserve public key extracted from the transaction details. + wad_id: WadId; +} + +export interface OutgoingHistory { + // Array of outgoing transactions. + outgoing_transactions: OutgoingBankTransaction[]; + + // Payto URI to identify the sender of funds. + // This must be one of the exchange's bank accounts. + // Credit account is shared by all incoming transactions + // as per the nature of the request. + + // undefined if outgoing transactions is empty + debit_account?: PaytoString; +} + +export interface OutgoingBankTransaction { + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: AmountString; + + // Payto URI to identify the receiver of funds. + credit_account: PaytoString; + + // The wire transfer ID in the outgoing transaction. + wtid: ShortHashCode; + + // Base URL of the exchange. + exchange_base_url: string; +} + +export interface AddIncomingRequest { + // Amount to transfer. + amount: AmountString; + + // Reserve public key that is included in the wire transfer details + // to identify the reserve that is being topped up. + reserve_pub: EddsaPublicKey; + + // Account (as payto URI) that makes the wire transfer to the exchange. + // Usually this account must be created by the test harness before this API is + // used. An exception is the "exchange-fakebank", where any debit account can be + // specified, as it is automatically created. + debit_account: PaytoString; +} + +export interface AddIncomingResponse { + // Timestamp that indicates when the wire transfer will be executed. + // In cases where the wire transfer gateway is unable to know when + // the wire transfer will be executed, the time at which the request + // has been received and stored will be returned. + // The purpose of this field is for debugging (humans trying to find + // the transaction) as well as for taxation (determining which + // time period a transaction belongs to). + timestamp: Timestamp; + + // Opaque ID of the transaction that the bank has made. + row_id: SafeUint64; +} + +export const codecForTransferResponse = + (): Codec<TalerWireGatewayApi.TransferResponse> => + buildCodecForObject<TalerWireGatewayApi.TransferResponse>() + .property("row_id", codecForNumber()) + .property("timestamp", codecForTimestamp) + .build("TalerWireGatewayApi.TransferResponse"); + +export const codecForIncomingHistory = + (): Codec<TalerWireGatewayApi.IncomingHistory> => + buildCodecForObject<TalerWireGatewayApi.IncomingHistory>() + .property("credit_account", codecForPaytoString()) + .property( + "incoming_transactions", + codecForList(codecForIncomingBankTransaction()), + ) + .build("TalerWireGatewayApi.IncomingHistory"); + +export const codecForIncomingBankTransaction = + (): Codec<TalerWireGatewayApi.IncomingBankTransaction> => + buildCodecForUnion<TalerWireGatewayApi.IncomingBankTransaction>() + .discriminateOn("type") + .alternative("RESERVE", codecForIncomingReserveTransaction()) + .alternative("WAD", codecForIncomingWadTransaction()) + .build("TalerWireGatewayApi.IncomingBankTransaction"); + +export const codecForIncomingReserveTransaction = + (): Codec<TalerWireGatewayApi.IncomingReserveTransaction> => + buildCodecForObject<TalerWireGatewayApi.IncomingReserveTransaction>() + .property("amount", codecForAmountString()) + .property("date", codecForTimestamp) + .property("debit_account", codecForPaytoString()) + .property("reserve_pub", codecForString()) + .property("row_id", codecForNumber()) + .property("type", codecForConstString("RESERVE")) + .build("TalerWireGatewayApi.IncomingReserveTransaction"); + +export const codecForIncomingWadTransaction = + (): Codec<TalerWireGatewayApi.IncomingWadTransaction> => + buildCodecForObject<TalerWireGatewayApi.IncomingWadTransaction>() + .property("amount", codecForAmountString()) + .property("credit_account", codecForPaytoString()) + .property("date", codecForTimestamp) + .property("debit_account", codecForPaytoString()) + .property("origin_exchange_url", codecForString()) + .property("row_id", codecForNumber()) + .property("type", codecForConstString("WAD")) + .property("wad_id", codecForString()) + .build("TalerWireGatewayApi.IncomingWadTransaction"); + +export const codecForOutgoingHistory = + (): Codec<TalerWireGatewayApi.OutgoingHistory> => + buildCodecForObject<TalerWireGatewayApi.OutgoingHistory>() + .property("debit_account", codecForPaytoString()) + .property( + "outgoing_transactions", + codecForList(codecForOutgoingBankTransaction()), + ) + .build("TalerWireGatewayApi.OutgoingHistory"); + +export const codecForOutgoingBankTransaction = + (): Codec<TalerWireGatewayApi.OutgoingBankTransaction> => + buildCodecForObject<TalerWireGatewayApi.OutgoingBankTransaction>() + .property("amount", codecForAmountString()) + .property("credit_account", codecForPaytoString()) + .property("date", codecForTimestamp) + .property("exchange_base_url", codecForString()) + .property("row_id", codecForNumber()) + .property("wtid", codecForString()) + .build("TalerWireGatewayApi.OutgoingBankTransaction"); + +export const codecForAddIncomingResponse = + (): Codec<TalerWireGatewayApi.AddIncomingResponse> => + buildCodecForObject<TalerWireGatewayApi.AddIncomingResponse>() + .property("row_id", codecForNumber()) + .property("timestamp", codecForTimestamp) + .build("TalerWireGatewayApi.AddIncomingResponse"); diff --git a/packages/taler-util/src/types-test.ts b/packages/taler-util/src/types-test.ts @@ -1,93 +0,0 @@ -/* - 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 { codecForMerchantContractTerms as codecForContractTerms } from "./taler-types.js"; - -test("contract terms validation", (t) => { - const c = { - nonce: "123123123", - 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", - merchant: { name: "Foo" }, - order_id: "test_order", - pay_deadline: { t_s: 42 }, - wire_transfer_deadline: { t_s: 42 }, - merchant_base_url: "https://example.com/pay", - products: [], - refund_deadline: { t_s: 42 }, - summary: "hello", - timestamp: { t_s: 42 }, - wire_method: "test", - }; - - codecForContractTerms().decode(c); - - const c1 = JSON.parse(JSON.stringify(c)); - c1.pay_deadline = "foo"; - - try { - codecForContractTerms().decode(c1); - } catch (e) { - t.pass(); - return; - } - - t.fail(); -}); - -test("contract terms validation (locations)", (t) => { - const c = { - nonce: "123123123", - 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", - merchant: { - name: "Foo", - address: { - country: "DE", - }, - }, - order_id: "test_order", - pay_deadline: { t_s: 42 }, - wire_transfer_deadline: { t_s: 42 }, - merchant_base_url: "https://example.com/pay", - products: [], - refund_deadline: { t_s: 42 }, - summary: "hello", - timestamp: { t_s: 42 }, - wire_method: "test", - delivery_location: { - country: "FR", - town: "Rennes", - }, - }; - - const r = codecForContractTerms().decode(c); - - t.assert(r.merchant.address?.country === "DE"); - t.assert(r.delivery_location?.country === "FR"); - t.assert(r.delivery_location?.town === "Rennes"); -}); diff --git a/packages/taler-util/src/types.test.ts b/packages/taler-util/src/types.test.ts @@ -0,0 +1,93 @@ +/* + 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 { codecForContractTerms } from "./index.js"; + +test("contract terms validation", (t) => { + const c = { + nonce: "123123123", + 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", + merchant: { name: "Foo" }, + order_id: "test_order", + pay_deadline: { t_s: 42 }, + wire_transfer_deadline: { t_s: 42 }, + merchant_base_url: "https://example.com/pay", + products: [], + refund_deadline: { t_s: 42 }, + summary: "hello", + timestamp: { t_s: 42 }, + wire_method: "test", + }; + + codecForContractTerms().decode(c); + + const c1 = JSON.parse(JSON.stringify(c)); + c1.pay_deadline = "foo"; + + try { + codecForContractTerms().decode(c1); + } catch (e) { + t.pass(); + return; + } + + t.fail(); +}); + +test("contract terms validation (locations)", (t) => { + const c = { + nonce: "123123123", + 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", + merchant: { + name: "Foo", + address: { + country: "DE", + }, + }, + order_id: "test_order", + pay_deadline: { t_s: 42 }, + wire_transfer_deadline: { t_s: 42 }, + merchant_base_url: "https://example.com/pay", + products: [], + refund_deadline: { t_s: 42 }, + summary: "hello", + timestamp: { t_s: 42 }, + wire_method: "test", + delivery_location: { + country: "FR", + town: "Rennes", + }, + }; + + const r = codecForContractTerms().decode(c); + + t.assert(r.merchant.address?.country === "DE"); + t.assert(r.delivery_location?.country === "FR"); + t.assert(r.delivery_location?.town === "Rennes"); +}); diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts @@ -1,3465 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2015-2020 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Types used by clients of the wallet. - * - * These types are defined in a separate file make tree shaking easier, since - * some components use these types (via RPC) but do not depend on the wallet - * code directly. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ -import { AmountJson, codecForAmountString } from "./amounts.js"; -import { BackupRecovery } from "./backup-types.js"; -import { - Codec, - Context, - DecodingError, - buildCodecForObject, - buildCodecForUnion, - codecForAny, - codecForBoolean, - codecForConstString, - codecForEither, - codecForList, - codecForMap, - codecForNumber, - codecForString, - codecOptional, - renderContext, -} from "./codec.js"; -import { - CurrencySpecification, - TalerMerchantApi, - TemplateParams, - WithdrawalOperationStatus, - canonicalizeBaseUrl, -} from "./index.js"; -import { VersionMatchResult } from "./libtool-version.js"; -import { PaytoString, PaytoUri, codecForPaytoString } from "./payto.js"; -import { QrCodeSpec } from "./qr.js"; -import { AgeCommitmentProof } from "./taler-crypto.js"; -import { TalerErrorCode } from "./taler-error-codes.js"; -import { - AccountRestriction, - AmountString, - AuditorDenomSig, - CoinEnvelope, - DenomKeyType, - DenominationPubKey, - EddsaPrivateKeyString, - ExchangeAuditor, - ExchangeWireAccount, - InternationalizedString, - MerchantContractTerms, - MerchantInfo, - PeerContractTerms, - UnblindedSignature, - codecForExchangeWireAccount, - codecForMerchantContractTerms, - codecForPeerContractTerms, -} from "./taler-types.js"; -import { - AbsoluteTime, - TalerPreciseTimestamp, - TalerProtocolDuration, - TalerProtocolTimestamp, - codecForAbsoluteTime, - codecForPreciseTimestamp, - codecForTimestamp, -} from "./time.js"; -import { - OrderShortInfo, - TransactionState, - TransactionType, -} from "./transactions-types.js"; - -/** - * Identifier for a transaction in the wallet. - */ -declare const __txId: unique symbol; -export type TransactionIdStr = `txn:${string}:${string}` & { [__txId]: true }; - -/** - * Identifier for a pending task in the wallet. - */ -declare const __pndId: unique symbol; -export type PendingIdStr = `pnd:${string}:${string}` & { [__pndId]: true }; - -declare const __tmbId: unique symbol; -export type TombstoneIdStr = `tmb:${string}:${string}` & { [__tmbId]: true }; - -function codecForTransactionIdStr(): Codec<TransactionIdStr> { - return { - decode(x: any, c?: Context): TransactionIdStr { - if (typeof x === "string" && x.startsWith("txn:")) { - return x as TransactionIdStr; - } - throw new DecodingError( - `expected string starting with "txn:" at ${renderContext( - c, - )} but got ${x}`, - ); - }, - }; -} - -function codecForPendingIdStr(): Codec<PendingIdStr> { - return { - decode(x: any, c?: Context): PendingIdStr { - if (typeof x === "string" && x.startsWith("txn:")) { - return x as PendingIdStr; - } - throw new DecodingError( - `expected string starting with "txn:" at ${renderContext( - c, - )} but got ${x}`, - ); - }, - }; -} - -function codecForTombstoneIdStr(): Codec<TombstoneIdStr> { - return { - decode(x: any, c?: Context): TombstoneIdStr { - if (typeof x === "string" && x.startsWith("tmb:")) { - return x as TombstoneIdStr; - } - throw new DecodingError( - `expected string starting with "tmb:" at ${renderContext( - c, - )} but got ${x}`, - ); - }, - }; -} - -export function codecForCanonBaseUrl(): Codec<string> { - return { - decode(x: any, c?: Context): string { - if (typeof x === "string") { - const canon = canonicalizeBaseUrl(x); - if (x !== canon) { - throw new DecodingError( - `expected canonicalized base URL at ${renderContext( - c, - )} but got value '${x}'`, - ); - } - return x; - } - throw new DecodingError( - `expected base URL at ${renderContext(c)} but got type ${typeof x}`, - ); - }, - }; -} - -/** - * Response for the create reserve request to the wallet. - */ -export class CreateReserveResponse { - /** - * Exchange URL where the bank should create the reserve. - * The URL is canonicalized in the response. - */ - exchange: string; - - /** - * Reserve public key of the newly created reserve. - */ - reservePub: string; -} - -export interface GetBalanceDetailRequest { - currency: string; -} - -export const codecForGetBalanceDetailRequest = - (): Codec<GetBalanceDetailRequest> => - buildCodecForObject<GetBalanceDetailRequest>() - .property("currency", codecForString()) - .build("GetBalanceDetailRequest"); - -/** - * How the amount should be interpreted in a transaction - * Effective = how the balance is change - * Raw = effective amount without fee - * - * Depending on the transaction, raw can be higher than effective - */ -export enum TransactionAmountMode { - Effective = "effective", - Raw = "raw", -} - -export type GetPlanForOperationRequest = - | GetPlanForWithdrawRequest - | GetPlanForDepositRequest; -// | GetPlanForPushDebitRequest -// | GetPlanForPullCreditRequest -// | GetPlanForPaymentRequest -// | GetPlanForTipRequest -// | GetPlanForRefundRequest -// | GetPlanForPullDebitRequest -// | GetPlanForPushCreditRequest; - -interface GetPlanForWalletInitiatedOperation { - instructedAmount: AmountString; - mode: TransactionAmountMode; -} - -export interface ConvertAmountRequest { - amount: AmountString; - type: TransactionAmountMode; - depositPaytoUri: PaytoString; -} - -export const codecForConvertAmountRequest = - buildCodecForObject<ConvertAmountRequest>() - .property("amount", codecForAmountString()) - .property("depositPaytoUri", codecForPaytoString()) - .property( - "type", - codecForEither( - codecForConstString(TransactionAmountMode.Raw), - codecForConstString(TransactionAmountMode.Effective), - ), - ) - .build("ConvertAmountRequest"); - -export interface GetAmountRequest { - currency: string; -} - -export const codecForGetAmountRequest = buildCodecForObject<GetAmountRequest>() - .property("currency", codecForString()) - .build("GetAmountRequest"); - -interface GetPlanToCompleteOperation { - instructedAmount: AmountString; -} - -const codecForGetPlanForWalletInitiatedOperation = < - T extends GetPlanForWalletInitiatedOperation, ->() => - buildCodecForObject<T>() - .property( - "mode", - codecForEither( - codecForConstString(TransactionAmountMode.Raw), - codecForConstString(TransactionAmountMode.Effective), - ), - ) - .property("instructedAmount", codecForAmountString()); - -interface GetPlanForWithdrawRequest extends GetPlanForWalletInitiatedOperation { - type: TransactionType.Withdrawal; - exchangeUrl?: string; -} -interface GetPlanForDepositRequest extends GetPlanForWalletInitiatedOperation { - type: TransactionType.Deposit; - account: string; //payto string -} -interface GetPlanForPushDebitRequest - extends GetPlanForWalletInitiatedOperation { - type: TransactionType.PeerPushDebit; -} - -interface GetPlanForPullCreditRequest - extends GetPlanForWalletInitiatedOperation { - type: TransactionType.PeerPullCredit; - exchangeUrl: string; -} - -const codecForGetPlanForWithdrawRequest = - codecForGetPlanForWalletInitiatedOperation<GetPlanForWithdrawRequest>() - .property("type", codecForConstString(TransactionType.Withdrawal)) - .property("exchangeUrl", codecOptional(codecForString())) - .build("GetPlanForWithdrawRequest"); - -const codecForGetPlanForDepositRequest = - codecForGetPlanForWalletInitiatedOperation<GetPlanForDepositRequest>() - .property("type", codecForConstString(TransactionType.Deposit)) - .property("account", codecForString()) - .build("GetPlanForDepositRequest"); - -const codecForGetPlanForPushDebitRequest = - codecForGetPlanForWalletInitiatedOperation<GetPlanForPushDebitRequest>() - .property("type", codecForConstString(TransactionType.PeerPushDebit)) - .build("GetPlanForPushDebitRequest"); - -const codecForGetPlanForPullCreditRequest = - codecForGetPlanForWalletInitiatedOperation<GetPlanForPullCreditRequest>() - .property("type", codecForConstString(TransactionType.PeerPullCredit)) - .property("exchangeUrl", codecForString()) - .build("GetPlanForPullCreditRequest"); - -interface GetPlanForPaymentRequest extends GetPlanToCompleteOperation { - type: TransactionType.Payment; - wireMethod: string; - ageRestriction: number; - maxDepositFee: AmountString; -} - -interface GetPlanForPullDebitRequest extends GetPlanToCompleteOperation { - type: TransactionType.PeerPullDebit; -} - -interface GetPlanForPushCreditRequest extends GetPlanToCompleteOperation { - type: TransactionType.PeerPushCredit; -} - -const codecForGetPlanForPaymentRequest = - buildCodecForObject<GetPlanForPaymentRequest>() - .property("type", codecForConstString(TransactionType.Payment)) - .property("maxDepositFee", codecForAmountString()) - .build("GetPlanForPaymentRequest"); - -const codecForGetPlanForPullDebitRequest = - buildCodecForObject<GetPlanForPullDebitRequest>() - .property("type", codecForConstString(TransactionType.PeerPullDebit)) - .build("GetPlanForPullDebitRequest"); - -const codecForGetPlanForPushCreditRequest = - buildCodecForObject<GetPlanForPushCreditRequest>() - .property("type", codecForConstString(TransactionType.PeerPushCredit)) - .build("GetPlanForPushCreditRequest"); - -export const codecForGetPlanForOperationRequest = - (): Codec<GetPlanForOperationRequest> => - buildCodecForUnion<GetPlanForOperationRequest>() - .discriminateOn("type") - .alternative( - TransactionType.Withdrawal, - codecForGetPlanForWithdrawRequest, - ) - .alternative(TransactionType.Deposit, codecForGetPlanForDepositRequest) - // .alternative( - // TransactionType.PeerPushDebit, - // codecForGetPlanForPushDebitRequest, - // ) - // .alternative( - // TransactionType.PeerPullCredit, - // codecForGetPlanForPullCreditRequest, - // ) - // .alternative(TransactionType.Payment, codecForGetPlanForPaymentRequest) - // .alternative( - // TransactionType.PeerPullDebit, - // codecForGetPlanForPullDebitRequest, - // ) - // .alternative( - // TransactionType.PeerPushCredit, - // codecForGetPlanForPushCreditRequest, - // ) - .build("GetPlanForOperationRequest"); - -export interface GetPlanForOperationResponse { - effectiveAmount: AmountString; - rawAmount: AmountString; - counterPartyAmount?: AmountString; - details: any; -} - -export const codecForGetPlanForOperationResponse = - (): Codec<GetPlanForOperationResponse> => - buildCodecForObject<GetPlanForOperationResponse>() - .property("effectiveAmount", codecForAmountString()) - .property("rawAmount", codecForAmountString()) - .property("details", codecForAny()) - .property("counterPartyAmount", codecOptional(codecForAmountString())) - .build("GetPlanForOperationResponse"); - -export interface AmountResponse { - effectiveAmount: AmountString; - rawAmount: AmountString; -} - -export const codecForAmountResponse = (): Codec<AmountResponse> => - buildCodecForObject<AmountResponse>() - .property("effectiveAmount", codecForAmountString()) - .property("rawAmount", codecForAmountString()) - .build("AmountResponse"); - -export enum BalanceFlag { - IncomingKyc = "incoming-kyc", - IncomingAml = "incoming-aml", - IncomingConfirmation = "incoming-confirmation", - OutgoingKyc = "outgoing-kyc", -} - -export interface WalletBalance { - scopeInfo: ScopeInfo; - available: AmountString; - pendingIncoming: AmountString; - pendingOutgoing: AmountString; - - /** - * Does the balance for this currency have a pending - * transaction? - * - * @deprecated use flags and pendingIncoming/pendingOutgoing instead - */ - hasPendingTransactions: boolean; - - /** - * Is there a transaction that requires user input? - * - * @deprecated use flags instead - */ - requiresUserInput: boolean; - - flags: BalanceFlag[]; -} - -export const codecForScopeInfoGlobal = (): Codec<ScopeInfoGlobal> => - buildCodecForObject<ScopeInfoGlobal>() - .property("currency", codecForString()) - .property("type", codecForConstString(ScopeType.Global)) - .build("ScopeInfoGlobal"); - -export const codecForScopeInfoExchange = (): Codec<ScopeInfoExchange> => - buildCodecForObject<ScopeInfoExchange>() - .property("currency", codecForString()) - .property("type", codecForConstString(ScopeType.Exchange)) - .property("url", codecForString()) - .build("ScopeInfoExchange"); - -export const codecForScopeInfoAuditor = (): Codec<ScopeInfoAuditor> => - buildCodecForObject<ScopeInfoAuditor>() - .property("currency", codecForString()) - .property("type", codecForConstString(ScopeType.Auditor)) - .property("url", codecForString()) - .build("ScopeInfoAuditor"); - -export const codecForScopeInfo = (): Codec<ScopeInfo> => - buildCodecForUnion<ScopeInfo>() - .discriminateOn("type") - .alternative(ScopeType.Global, codecForScopeInfoGlobal()) - .alternative(ScopeType.Exchange, codecForScopeInfoExchange()) - .alternative(ScopeType.Auditor, codecForScopeInfoAuditor()) - .build("ScopeInfo"); - -export interface GetCurrencySpecificationRequest { - scope: ScopeInfo; -} - -export const codecForGetCurrencyInfoRequest = - (): Codec<GetCurrencySpecificationRequest> => - buildCodecForObject<GetCurrencySpecificationRequest>() - .property("scope", codecForScopeInfo()) - .build("GetCurrencySpecificationRequest"); - -export interface ListExchangesForScopedCurrencyRequest { - scope: ScopeInfo; -} - -export const codecForListExchangesForScopedCurrencyRequest = - (): Codec<ListExchangesForScopedCurrencyRequest> => - buildCodecForObject<ListExchangesForScopedCurrencyRequest>() - .property("scope", codecForScopeInfo()) - .build("ListExchangesForScopedCurrencyRequest"); - -export interface GetCurrencySpecificationResponse { - currencySpecification: CurrencySpecification; -} - -export interface BuiltinExchange { - exchangeBaseUrl: string; - currencyHint: string; -} - -export interface PartialWalletRunConfig { - builtin?: Partial<WalletRunConfig["builtin"]>; - testing?: Partial<WalletRunConfig["testing"]>; - features?: Partial<WalletRunConfig["features"]>; - lazyTaskLoop?: Partial<WalletRunConfig["lazyTaskLoop"]>; -} - -export interface WalletRunConfig { - /** - * Initialization values useful for a complete startup. - * - * These are values may be overridden by different wallets - */ - builtin: { - exchanges: BuiltinExchange[]; - }; - - /** - * Unsafe options which it should only be used to create - * testing environment. - */ - testing: { - /** - * Allow withdrawal of denominations even though they are about to expire. - */ - denomselAllowLate: boolean; - devModeActive: boolean; - insecureTrustExchange: boolean; - preventThrottling: boolean; - skipDefaults: boolean; - emitObservabilityEvents?: boolean; - }; - - /** - * Configurations values that may be safe to show to the user - */ - features: { - allowHttp: boolean; - }; - - /** - * Start processing tasks only when explicitly required, even after - * init has been called. - * - * Useful when the wallet is started to make single read-only request, - * as otherwise wallet-core starts making network request and process - * unrelated pending tasks. - */ - lazyTaskLoop: boolean; -} - -export interface InitRequest { - config?: PartialWalletRunConfig; -} - -export const codecForInitRequest = (): Codec<InitRequest> => - buildCodecForObject<InitRequest>() - .property("config", codecForAny()) - .build("InitRequest"); - -export interface InitResponse { - versionInfo: WalletCoreVersion; -} - -export enum ScopeType { - Global = "global", - Exchange = "exchange", - Auditor = "auditor", -} - -export type ScopeInfoGlobal = { type: ScopeType.Global; currency: string }; - -export type ScopeInfoExchange = { - type: ScopeType.Exchange; - currency: string; - url: string; -}; - -export type ScopeInfoAuditor = { - type: ScopeType.Auditor; - currency: string; - url: string; -}; - -export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; - -/** - * Encode scope info as a string. - * - * Format must be stable as it's used in the database. - */ -export function stringifyScopeInfo(si: ScopeInfo): string { - switch (si.type) { - case ScopeType.Global: - return `taler-si:global/${si.currency}}`; - case ScopeType.Auditor: - return `taler-si:auditor/${si.currency}/${encodeURIComponent(si.url)}`; - case ScopeType.Exchange: - return `taler-si:exchange/${si.currency}/${encodeURIComponent(si.url)}`; - } -} - -export interface BalancesResponse { - balances: WalletBalance[]; -} - -export const codecForBalance = (): Codec<WalletBalance> => - buildCodecForObject<WalletBalance>() - .property("scopeInfo", codecForAny()) // FIXME - .property("available", codecForAmountString()) - .property("hasPendingTransactions", codecForBoolean()) - .property("pendingIncoming", codecForAmountString()) - .property("pendingOutgoing", codecForAmountString()) - .property("requiresUserInput", codecForBoolean()) - .property("flags", codecForAny()) // FIXME - .build("Balance"); - -export const codecForBalancesResponse = (): Codec<BalancesResponse> => - buildCodecForObject<BalancesResponse>() - .property("balances", codecForList(codecForBalance())) - .build("BalancesResponse"); - -/** - * For terseness. - */ -export function mkAmount( - value: number, - fraction: number, - currency: string, -): AmountJson { - return { value, fraction, currency }; -} - -/** - * Status of a coin. - */ -export enum CoinStatus { - /** - * Withdrawn and never shown to anybody. - */ - Fresh = "fresh", - - /** - * Coin was lost as the denomination is not usable anymore. - */ - DenomLoss = "denom-loss", - - /** - * Fresh, but currently marked as "suspended", thus won't be used - * for spending. Used for testing. - */ - FreshSuspended = "fresh-suspended", - - /** - * A coin that has been spent and refreshed. - */ - Dormant = "dormant", -} - -export type WalletCoinHistoryItem = - | { - type: "withdraw"; - transactionId: TransactionIdStr; - } - | { - type: "spend"; - transactionId: TransactionIdStr; - amount: AmountString; - } - | { - type: "refresh"; - transactionId: TransactionIdStr; - amount: AmountString; - } - | { - type: "recoup"; - transactionId: TransactionIdStr; - amount: AmountString; - } - | { - type: "refund"; - transactionId: TransactionIdStr; - amount: AmountString; - }; - -/** - * Easy to process format for the public data of coins - * managed by the wallet. - */ -export interface CoinDumpJson { - coins: Array<{ - /** - * The coin's denomination's public key. - */ - denomPub: DenominationPubKey; - /** - * Hash of denom_pub. - */ - denomPubHash: string; - /** - * Value of the denomination (without any fees). - */ - denomValue: string; - /** - * Public key of the coin. - */ - coinPub: string; - /** - * Base URL of the exchange for the coin. - */ - exchangeBaseUrl: string; - /** - * Public key of the parent coin. - * Only present if this coin was obtained via refreshing. - */ - refreshParentCoinPub: string | undefined; - /** - * Public key of the reserve for this coin. - * Only present if this coin was obtained via refreshing. - */ - withdrawalReservePub: string | undefined; - /** - * Status of the coin. - */ - coinStatus: CoinStatus; - /** - * Information about the age restriction - */ - ageCommitmentProof: AgeCommitmentProof | undefined; - history: WalletCoinHistoryItem[]; - }>; -} - -export enum ConfirmPayResultType { - Done = "done", - Pending = "pending", -} - -/** - * Result for confirmPay - */ -export interface ConfirmPayResultDone { - type: ConfirmPayResultType.Done; - contractTerms: MerchantContractTerms; - transactionId: TransactionIdStr; -} - -export interface ConfirmPayResultPending { - type: ConfirmPayResultType.Pending; - transactionId: TransactionIdStr; - lastError: TalerErrorDetail | undefined; -} - -export const codecForTalerErrorDetail = (): Codec<TalerErrorDetail> => - buildCodecForObject<TalerErrorDetail>() - .property("code", codecForNumber()) - .property("when", codecOptional(codecForAbsoluteTime)) - .property("hint", codecOptional(codecForString())) - .build("TalerErrorDetail"); - -export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending; - -export const codecForConfirmPayResultPending = - (): Codec<ConfirmPayResultPending> => - buildCodecForObject<ConfirmPayResultPending>() - .property("lastError", codecOptional(codecForTalerErrorDetail())) - .property("transactionId", codecForTransactionIdStr()) - .property("type", codecForConstString(ConfirmPayResultType.Pending)) - .build("ConfirmPayResultPending"); - -export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> => - buildCodecForObject<ConfirmPayResultDone>() - .property("type", codecForConstString(ConfirmPayResultType.Done)) - .property("transactionId", codecForTransactionIdStr()) - .property("contractTerms", codecForMerchantContractTerms()) - .build("ConfirmPayResultDone"); - -export const codecForConfirmPayResult = (): Codec<ConfirmPayResult> => - buildCodecForUnion<ConfirmPayResult>() - .discriminateOn("type") - .alternative( - ConfirmPayResultType.Pending, - codecForConfirmPayResultPending(), - ) - .alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone()) - .build("ConfirmPayResult"); - -/** - * Information about all sender wire details known to the wallet, - * as well as exchanges that accept these wire types. - */ -export interface SenderWireInfos { - /** - * Mapping from exchange base url to list of accepted - * wire types. - */ - exchangeWireTypes: { [exchangeBaseUrl: string]: string[] }; - - /** - * Sender wire information stored in the wallet. - */ - senderWires: string[]; -} - -/** - * Request to mark a reserve as confirmed. - */ -export interface ConfirmReserveRequest { - /** - * Public key of then reserve that should be marked - * as confirmed. - */ - reservePub: string; -} - -export const codecForConfirmReserveRequest = (): Codec<ConfirmReserveRequest> => - buildCodecForObject<ConfirmReserveRequest>() - .property("reservePub", codecForString()) - .build("ConfirmReserveRequest"); - -export interface PrepareRefundResult { - proposalId: string; - - effectivePaid: AmountString; - gone: AmountString; - granted: AmountString; - pending: boolean; - awaiting: AmountString; - - info: OrderShortInfo; -} - -export interface BenchmarkResult { - time: { [s: string]: number }; - repetitions: number; -} - -export enum PreparePayResultType { - PaymentPossible = "payment-possible", - InsufficientBalance = "insufficient-balance", - AlreadyConfirmed = "already-confirmed", -} - -export const codecForPreparePayResultPaymentPossible = - (): Codec<PreparePayResultPaymentPossible> => - buildCodecForObject<PreparePayResultPaymentPossible>() - .property("amountEffective", codecForAmountString()) - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForMerchantContractTerms()) - .property("transactionId", codecForTransactionIdStr()) - .property("proposalId", codecForString()) - .property("contractTermsHash", codecForString()) - .property("talerUri", codecForString()) - .property( - "status", - codecForConstString(PreparePayResultType.PaymentPossible), - ) - .build("PreparePayResultPaymentPossible"); - -export interface BalanceDetails {} - -/** - * Detailed reason for why the wallet's balance is insufficient. - */ -export interface PaymentInsufficientBalanceDetails { - /** - * Amount requested by the merchant. - */ - amountRequested: AmountString; - - /** - * Balance of type "available" (see balance.ts for definition). - */ - balanceAvailable: AmountString; - - /** - * Balance of type "material" (see balance.ts for definition). - */ - balanceMaterial: AmountString; - - /** - * Balance of type "age-acceptable" (see balance.ts for definition). - */ - balanceAgeAcceptable: AmountString; - - /** - * Balance of type "merchant-acceptable" (see balance.ts for definition). - */ - balanceReceiverAcceptable: AmountString; - - /** - * Balance of type "merchant-depositable" (see balance.ts for definition). - */ - balanceReceiverDepositable: AmountString; - - balanceExchangeDepositable: AmountString; - - /** - * Maximum effective amount that the wallet can spend, - * when all fees are paid by the wallet. - */ - maxEffectiveSpendAmount: AmountString; - - perExchange: { - [url: string]: { - balanceAvailable: AmountString; - balanceMaterial: AmountString; - balanceExchangeDepositable: AmountString; - balanceAgeAcceptable: AmountString; - balanceReceiverAcceptable: AmountString; - balanceReceiverDepositable: AmountString; - maxEffectiveSpendAmount: AmountString; - /** - * Exchange doesn't have global fees configured for the relevant year, - * p2p payments aren't possible. - */ - missingGlobalFees: boolean; - }; - }; -} - -export const codecForPayMerchantInsufficientBalanceDetails = - (): Codec<PaymentInsufficientBalanceDetails> => - buildCodecForObject<PaymentInsufficientBalanceDetails>() - .property("amountRequested", codecForAmountString()) - .property("balanceAgeAcceptable", codecForAmountString()) - .property("balanceAvailable", codecForAmountString()) - .property("balanceMaterial", codecForAmountString()) - .property("balanceReceiverAcceptable", codecForAmountString()) - .property("balanceReceiverDepositable", codecForAmountString()) - .property("balanceExchangeDepositable", codecForAmountString()) - .property("perExchange", codecForAny()) - .property("maxEffectiveSpendAmount", codecForAmountString()) - .build("PayMerchantInsufficientBalanceDetails"); - -export const codecForPreparePayResultInsufficientBalance = - (): Codec<PreparePayResultInsufficientBalance> => - buildCodecForObject<PreparePayResultInsufficientBalance>() - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForAny()) - .property("talerUri", codecForString()) - .property("proposalId", codecForString()) - .property("transactionId", codecForTransactionIdStr()) - .property( - "status", - codecForConstString(PreparePayResultType.InsufficientBalance), - ) - .property( - "balanceDetails", - codecForPayMerchantInsufficientBalanceDetails(), - ) - .build("PreparePayResultInsufficientBalance"); - -export const codecForPreparePayResultAlreadyConfirmed = - (): Codec<PreparePayResultAlreadyConfirmed> => - buildCodecForObject<PreparePayResultAlreadyConfirmed>() - .property( - "status", - codecForConstString(PreparePayResultType.AlreadyConfirmed), - ) - .property("amountEffective", codecOptional(codecForAmountString())) - .property("amountRaw", codecForAmountString()) - .property("paid", codecForBoolean()) - .property("talerUri", codecForString()) - .property("contractTerms", codecForAny()) - .property("contractTermsHash", codecForString()) - .property("transactionId", codecForTransactionIdStr()) - .property("proposalId", codecForString()) - .build("PreparePayResultAlreadyConfirmed"); - -export const codecForPreparePayResult = (): Codec<PreparePayResult> => - buildCodecForUnion<PreparePayResult>() - .discriminateOn("status") - .alternative( - PreparePayResultType.AlreadyConfirmed, - codecForPreparePayResultAlreadyConfirmed(), - ) - .alternative( - PreparePayResultType.InsufficientBalance, - codecForPreparePayResultInsufficientBalance(), - ) - .alternative( - PreparePayResultType.PaymentPossible, - codecForPreparePayResultPaymentPossible(), - ) - .build("PreparePayResult"); - -/** - * Result of a prepare pay operation. - */ -export type PreparePayResult = - | PreparePayResultInsufficientBalance - | PreparePayResultAlreadyConfirmed - | PreparePayResultPaymentPossible; - -/** - * Payment is possible. - */ -export interface PreparePayResultPaymentPossible { - status: PreparePayResultType.PaymentPossible; - transactionId: TransactionIdStr; - /** - * @deprecated use transactionId instead - */ - proposalId: string; - contractTerms: MerchantContractTerms; - contractTermsHash: string; - amountRaw: AmountString; - amountEffective: AmountString; - talerUri: string; -} - -export interface PreparePayResultInsufficientBalance { - status: PreparePayResultType.InsufficientBalance; - transactionId: TransactionIdStr; - /** - * @deprecated use transactionId - */ - proposalId: string; - contractTerms: MerchantContractTerms; - amountRaw: AmountString; - talerUri: string; - balanceDetails: PaymentInsufficientBalanceDetails; -} - -export interface PreparePayResultAlreadyConfirmed { - status: PreparePayResultType.AlreadyConfirmed; - transactionId: TransactionIdStr; - contractTerms: MerchantContractTerms; - paid: boolean; - amountRaw: AmountString; - amountEffective: AmountString | undefined; - contractTermsHash: string; - /** - * @deprecated use transactionId - */ - proposalId: string; - talerUri: string; -} - -export interface BankWithdrawDetails { - status: WithdrawalOperationStatus; - currency: string; - amount: AmountJson | undefined; - editableAmount: boolean; - maxAmount: AmountJson | undefined; - wireFee: AmountJson | undefined; - senderWire?: string; - exchange?: string; - editableExchange: boolean; - confirmTransferUrl?: string; - wireTypes: string[]; - operationId: string; - apiBaseUrl: string; -} - -export interface AcceptWithdrawalResponse { - reservePub: string; - confirmTransferUrl?: string; - transactionId: TransactionIdStr; -} - -/** - * Details about a purchase, including refund status. - */ -export interface PurchaseDetails { - contractTerms: Record<string, undefined>; - hasRefund: boolean; - totalRefundAmount: AmountJson; - totalRefundAndRefreshFees: AmountJson; -} - -export interface WalletDiagnostics { - walletManifestVersion: string; - walletManifestDisplayVersion: string; - errors: string[]; - firefoxIdbProblem: boolean; - dbOutdated: boolean; -} - -export interface TalerErrorDetail { - code: TalerErrorCode; - when?: AbsoluteTime; - hint?: string; - [x: string]: unknown; -} - -/** - * Minimal information needed about a planchet for unblinding a signature. - * - * Can be a withdrawal/refresh planchet. - */ -export interface PlanchetUnblindInfo { - denomPub: DenominationPubKey; - blindingKey: string; -} - -export interface WithdrawalPlanchet { - coinPub: string; - coinPriv: string; - reservePub: string; - denomPubHash: string; - denomPub: DenominationPubKey; - blindingKey: string; - withdrawSig: string; - coinEv: CoinEnvelope; - coinValue: AmountJson; - coinEvHash: string; - ageCommitmentProof?: AgeCommitmentProof; -} - -export interface PlanchetCreationRequest { - secretSeed: string; - coinIndex: number; - value: AmountJson; - feeWithdraw: AmountJson; - denomPub: DenominationPubKey; - reservePub: string; - reservePriv: string; - restrictAge?: number; -} - -/** - * Reasons for why a coin is being refreshed. - */ -export enum RefreshReason { - Manual = "manual", - PayMerchant = "pay-merchant", - PayDeposit = "pay-deposit", - PayPeerPush = "pay-peer-push", - PayPeerPull = "pay-peer-pull", - Refund = "refund", - AbortPay = "abort-pay", - AbortDeposit = "abort-deposit", - AbortPeerPushDebit = "abort-peer-push-debit", - AbortPeerPullDebit = "abort-peer-pull-debit", - Recoup = "recoup", - BackupRestored = "backup-restored", - Scheduled = "scheduled", -} - -/** - * Request to refresh a single coin. - */ -export interface CoinRefreshRequest { - readonly coinPub: string; - readonly amount: AmountString; -} - -/** - * Private data required to make a deposit permission. - */ -export interface DepositInfo { - exchangeBaseUrl: string; - contractTermsHash: string; - coinPub: string; - coinPriv: string; - spendAmount: AmountJson; - timestamp: TalerProtocolTimestamp; - refundDeadline: TalerProtocolTimestamp; - merchantPub: string; - feeDeposit: AmountJson; - wireInfoHash: string; - denomKeyType: DenomKeyType; - denomPubHash: string; - denomSig: UnblindedSignature; - - requiredMinimumAge?: number; - - ageCommitmentProof?: AgeCommitmentProof; -} - -export interface ExchangesShortListResponse { - exchanges: ShortExchangeListItem[]; -} - -export interface ExchangesListResponse { - exchanges: ExchangeListItem[]; -} - -export interface ExchangeDetailedResponse { - exchange: ExchangeFullDetails; -} - -export interface WalletCoreVersion { - implementationSemver: string; - implementationGitHash: string; - - /** - * Wallet-core protocol version supported by this implementation - * of the API ("server" version). - */ - version: string; - exchange: string; - merchant: string; - - bankIntegrationApiRange: string; - bankConversionApiRange: string; - corebankApiRange: string; - - /** - * @deprecated as bank was split into multiple APIs with separate versioning - */ - bank: string; - - /** - * @deprecated - */ - hash: string | undefined; - - /** - * @deprecated will be removed - */ - devMode: boolean; -} - -export interface KnownBankAccountsInfo { - uri: PaytoUri; - kyc_completed: boolean; - currency: string; - alias: string; -} - -export interface KnownBankAccounts { - accounts: KnownBankAccountsInfo[]; -} - -/** - * Wire fee for one wire method - */ -export interface WireFee { - /** - * Fee for wire transfers. - */ - wireFee: AmountString; - - /** - * Fees to close and refund a reserve. - */ - closingFee: AmountString; - - /** - * Start date of the fee. - */ - startStamp: TalerProtocolTimestamp; - - /** - * End date of the fee. - */ - endStamp: TalerProtocolTimestamp; - - /** - * Signature made by the exchange master key. - */ - sig: string; -} - -export type WireFeeMap = { [wireMethod: string]: WireFee[] }; - -export interface WireInfo { - feesForType: WireFeeMap; - accounts: ExchangeWireAccount[]; -} - -export interface ExchangeGlobalFees { - startDate: TalerProtocolTimestamp; - endDate: TalerProtocolTimestamp; - - historyFee: AmountString; - accountFee: AmountString; - purseFee: AmountString; - - historyTimeout: TalerProtocolDuration; - purseTimeout: TalerProtocolDuration; - - purseLimit: number; - - signature: string; -} - -const codecForWireFee = (): Codec<WireFee> => - buildCodecForObject<WireFee>() - .property("sig", codecForString()) - .property("wireFee", codecForAmountString()) - .property("closingFee", codecForAmountString()) - .property("startStamp", codecForTimestamp) - .property("endStamp", codecForTimestamp) - .build("codecForWireFee"); - -const codecForWireInfo = (): Codec<WireInfo> => - buildCodecForObject<WireInfo>() - .property("feesForType", codecForMap(codecForList(codecForWireFee()))) - .property("accounts", codecForList(codecForExchangeWireAccount())) - .build("codecForWireInfo"); - -export interface DenominationInfo { - /** - * Value of one coin of the denomination. - */ - value: AmountString; - - /** - * Hash of the denomination public key. - * Stored in the database for faster lookups. - */ - denomPubHash: string; - - denomPub: DenominationPubKey; - - /** - * Fee for withdrawing. - */ - feeWithdraw: AmountString; - - /** - * Fee for depositing. - */ - feeDeposit: AmountString; - - /** - * Fee for refreshing. - */ - feeRefresh: AmountString; - - /** - * Fee for refunding. - */ - feeRefund: AmountString; - - /** - * Validity start date of the denomination. - */ - stampStart: TalerProtocolTimestamp; - - /** - * Date after which the currency can't be withdrawn anymore. - */ - stampExpireWithdraw: TalerProtocolTimestamp; - - /** - * Date after the denomination officially doesn't exist anymore. - */ - stampExpireLegal: TalerProtocolTimestamp; - - /** - * Data after which coins of this denomination can't be deposited anymore. - */ - stampExpireDeposit: TalerProtocolTimestamp; - - exchangeBaseUrl: string; -} - -export type DenomOperation = "deposit" | "withdraw" | "refresh" | "refund"; -export type DenomOperationMap<T> = { [op in DenomOperation]: T }; - -export interface FeeDescription { - group: string; - from: AbsoluteTime; - until: AbsoluteTime; - fee?: AmountString; -} - -export interface FeeDescriptionPair { - group: string; - from: AbsoluteTime; - until: AbsoluteTime; - left?: AmountString; - right?: AmountString; -} - -export interface TimePoint<T> { - id: string; - group: string; - fee: AmountString; - type: "start" | "end"; - moment: AbsoluteTime; - denom: T; -} - -export interface ExchangeFullDetails { - exchangeBaseUrl: string; - currency: string; - paytoUris: string[]; - auditors: ExchangeAuditor[]; - wireInfo: WireInfo; - denomFees: DenomOperationMap<FeeDescription[]>; - transferFees: Record<string, FeeDescription[]>; - globalFees: FeeDescription[]; -} - -export enum ExchangeTosStatus { - Pending = "pending", - Proposed = "proposed", - Accepted = "accepted", - MissingTos = "missing-tos", -} - -export enum ExchangeEntryStatus { - Preset = "preset", - Ephemeral = "ephemeral", - Used = "used", -} - -export enum ExchangeUpdateStatus { - Initial = "initial", - InitialUpdate = "initial-update", - Suspended = "suspended", - UnavailableUpdate = "unavailable-update", - Ready = "ready", - ReadyUpdate = "ready-update", -} - -export interface OperationErrorInfo { - error: TalerErrorDetail; -} - -export interface ShortExchangeListItem { - exchangeBaseUrl: string; -} - -/** - * Info about an exchange entry in the wallet. - */ -export interface ExchangeListItem { - exchangeBaseUrl: string; - masterPub: string | undefined; - currency: string; - paytoUris: string[]; - tosStatus: ExchangeTosStatus; - exchangeEntryStatus: ExchangeEntryStatus; - exchangeUpdateStatus: ExchangeUpdateStatus; - ageRestrictionOptions: number[]; - - /** - * P2P payments are disabled with this exchange - * (e.g. because no global fees are configured). - */ - peerPaymentsDisabled: boolean; - - /** - * Set to true if this exchange doesn't charge any fees. - */ - noFees: boolean; - - scopeInfo: ScopeInfo; - - lastUpdateTimestamp: TalerPreciseTimestamp | undefined; - - /** - * Information about the last error that occurred when trying - * to update the exchange info. - */ - lastUpdateErrorInfo?: OperationErrorInfo; - - unavailableReason?: TalerErrorDetail; -} - -const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => - buildCodecForObject<AuditorDenomSig>() - .property("denom_pub_h", codecForString()) - .property("auditor_sig", codecForString()) - .build("AuditorDenomSig"); - -const codecForExchangeAuditor = (): Codec<ExchangeAuditor> => - buildCodecForObject<ExchangeAuditor>() - .property("auditor_pub", codecForString()) - .property("auditor_url", codecForString()) - .property("denomination_keys", codecForList(codecForAuditorDenomSig())) - .build("codecForExchangeAuditor"); - -export const codecForFeeDescriptionPair = (): Codec<FeeDescriptionPair> => - buildCodecForObject<FeeDescriptionPair>() - .property("group", codecForString()) - .property("from", codecForAbsoluteTime) - .property("until", codecForAbsoluteTime) - .property("left", codecOptional(codecForAmountString())) - .property("right", codecOptional(codecForAmountString())) - .build("FeeDescriptionPair"); - -export const codecForFeeDescription = (): Codec<FeeDescription> => - buildCodecForObject<FeeDescription>() - .property("group", codecForString()) - .property("from", codecForAbsoluteTime) - .property("until", codecForAbsoluteTime) - .property("fee", codecOptional(codecForAmountString())) - .build("FeeDescription"); - -export const codecForFeesByOperations = (): Codec< - DenomOperationMap<FeeDescription[]> -> => - buildCodecForObject<DenomOperationMap<FeeDescription[]>>() - .property("deposit", codecForList(codecForFeeDescription())) - .property("withdraw", codecForList(codecForFeeDescription())) - .property("refresh", codecForList(codecForFeeDescription())) - .property("refund", codecForList(codecForFeeDescription())) - .build("DenomOperationMap"); - -export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> => - buildCodecForObject<ExchangeFullDetails>() - .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("paytoUris", codecForList(codecForString())) - .property("auditors", codecForList(codecForExchangeAuditor())) - .property("wireInfo", codecForWireInfo()) - .property("denomFees", codecForFeesByOperations()) - .property( - "transferFees", - codecForMap(codecForList(codecForFeeDescription())), - ) - .property("globalFees", codecForList(codecForFeeDescription())) - .build("ExchangeFullDetails"); - -export const codecForExchangeListItem = (): Codec<ExchangeListItem> => - buildCodecForObject<ExchangeListItem>() - .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("masterPub", codecOptional(codecForString())) - .property("paytoUris", codecForList(codecForString())) - .property("tosStatus", codecForAny()) - .property("exchangeEntryStatus", codecForAny()) - .property("exchangeUpdateStatus", codecForAny()) - .property("ageRestrictionOptions", codecForList(codecForNumber())) - .property("scopeInfo", codecForScopeInfo()) - .property("lastUpdateErrorInfo", codecForAny()) - .property("lastUpdateTimestamp", codecOptional(codecForPreciseTimestamp)) - .property("noFees", codecForBoolean()) - .property("peerPaymentsDisabled", codecForBoolean()) - .build("ExchangeListItem"); - -export const codecForExchangesListResponse = (): Codec<ExchangesListResponse> => - buildCodecForObject<ExchangesListResponse>() - .property("exchanges", codecForList(codecForExchangeListItem())) - .build("ExchangesListResponse"); - -export interface AcceptManualWithdrawalResult { - /** - * Payto URIs that can be used to fund the withdrawal. - * - * @deprecated in favor of withdrawalAccountsList - */ - exchangePaytoUris: string[]; - - /** - * Public key of the newly created reserve. - */ - reservePub: string; - - withdrawalAccountsList: WithdrawalExchangeAccountDetails[]; - - transactionId: TransactionIdStr; -} - -export interface WithdrawalDetailsForAmount { - /** - * Did the user accept the current version of the exchange's - * terms of service? - * - * @deprecated the client should query the exchange entry instead - */ - tosAccepted: boolean; - - /** - * Amount that the user will transfer to the exchange. - */ - amountRaw: AmountString; - - /** - * Amount that will be added to the user's wallet balance. - */ - amountEffective: AmountString; - - /** - * Number of coins that would be used for withdrawal. - * - * The UIs should warn if this number is too high (roughly at >100). - */ - numCoins: number; - - /** - * Ways to pay the exchange. - * - * @deprecated in favor of withdrawalAccountsList - */ - paytoUris: string[]; - - /** - * Ways to pay the exchange, including accounts that require currency conversion. - */ - withdrawalAccountsList: WithdrawalExchangeAccountDetails[]; - - /** - * If the exchange supports age-restricted coins it will return - * the array of ages. - */ - ageRestrictionOptions?: number[]; - - /** - * Scope info of the currency withdrawn. - */ - scopeInfo: ScopeInfo; -} - -export interface DenomSelItem { - denomPubHash: string; - count: number; - /** - * Number of denoms/planchets to skip, because - * a re-denomination effectively deleted them. - */ - skip?: number; -} - -/** - * Selected denominations withn some extra info. - */ -export interface DenomSelectionState { - totalCoinValue: AmountString; - totalWithdrawCost: AmountString; - selectedDenoms: DenomSelItem[]; - earliestDepositExpiration: TalerProtocolTimestamp; - hasDenomWithAgeRestriction: boolean; -} - -/** - * Information about what will happen doing a withdrawal. - * - * Sent to the wallet frontend to be rendered and shown to the user. - */ -export interface ExchangeWithdrawalDetails { - exchangePaytoUris: string[]; - - /** - * Filtered wire info to send to the bank. - */ - exchangeWireAccounts: string[]; - - exchangeCreditAccountDetails: WithdrawalExchangeAccountDetails[]; - - /** - * Selected denominations for withdraw. - */ - selectedDenoms: DenomSelectionState; - - /** - * Did the user already accept the current terms of service for the exchange? - */ - termsOfServiceAccepted: boolean; - - /** - * The earliest deposit expiration of the selected coins. - */ - earliestDepositExpiration: TalerProtocolTimestamp; - - /** - * Result of checking the wallet's version - * against the exchange's version. - * - * Older exchanges don't return version information. - */ - versionMatch: 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; - - /** - * Amount that will be subtracted from the reserve's balance. - */ - withdrawalAmountRaw: AmountString; - - /** - * Amount that will actually be added to the wallet's balance. - */ - withdrawalAmountEffective: AmountString; - - /** - * If the exchange supports age-restricted coins it will return - * the array of ages. - * - */ - ageRestrictionOptions?: number[]; - - scopeInfo: ScopeInfo; -} - -export interface GetExchangeTosResult { - /** - * Markdown version of the current ToS. - */ - content: string; - - /** - * Version tag of the current ToS. - */ - currentEtag: string; - - /** - * Version tag of the last ToS that the user has accepted, - * if any. - */ - acceptedEtag: string | undefined; - - /** - * Accepted content type - */ - contentType: string; - - /** - * Language of the returned content. - * - * If missing, language is unknown. - */ - contentLanguage: string | undefined; - - /** - * Available languages as advertised by the exchange. - */ - tosAvailableLanguages: string[]; - - tosStatus: ExchangeTosStatus; -} - -export interface TestPayArgs { - merchantBaseUrl: string; - merchantAuthToken?: string; - amount: AmountString; - summary: string; - forcedCoinSel?: ForcedCoinSel; -} - -export const codecForTestPayArgs = (): Codec<TestPayArgs> => - buildCodecForObject<TestPayArgs>() - .property("merchantBaseUrl", codecForCanonBaseUrl()) - .property("merchantAuthToken", codecOptional(codecForString())) - .property("amount", codecForAmountString()) - .property("summary", codecForString()) - .property("forcedCoinSel", codecForAny()) - .build("TestPayArgs"); - -export interface IntegrationTestArgs { - exchangeBaseUrl: string; - corebankApiBaseUrl: string; - merchantBaseUrl: string; - merchantAuthToken?: string; - amountToWithdraw: AmountString; - amountToSpend: AmountString; -} - -export const codecForIntegrationTestArgs = (): Codec<IntegrationTestArgs> => - buildCodecForObject<IntegrationTestArgs>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("merchantBaseUrl", codecForCanonBaseUrl()) - .property("merchantAuthToken", codecOptional(codecForString())) - .property("amountToSpend", codecForAmountString()) - .property("amountToWithdraw", codecForAmountString()) - .property("corebankApiBaseUrl", codecForCanonBaseUrl()) - .build("IntegrationTestArgs"); - -export interface IntegrationTestV2Args { - exchangeBaseUrl: string; - corebankApiBaseUrl: string; - merchantBaseUrl: string; - merchantAuthToken?: string; -} - -export const codecForIntegrationTestV2Args = (): Codec<IntegrationTestV2Args> => - buildCodecForObject<IntegrationTestV2Args>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("merchantBaseUrl", codecForCanonBaseUrl()) - .property("merchantAuthToken", codecOptional(codecForString())) - .property("corebankApiBaseUrl", codecForCanonBaseUrl()) - .build("IntegrationTestV2Args"); - -export interface GetExchangeEntryByUrlRequest { - exchangeBaseUrl: string; -} - -export const codecForGetExchangeEntryByUrlRequest = - (): Codec<GetExchangeEntryByUrlRequest> => - buildCodecForObject<GetExchangeEntryByUrlRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .build("GetExchangeEntryByUrlRequest"); - -export type GetExchangeEntryByUrlResponse = ExchangeListItem; - -export interface AddExchangeRequest { - exchangeBaseUrl: string; - - /** - * @deprecated use a separate API call to start a forced exchange update instead - */ - forceUpdate?: boolean; -} - -export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => - buildCodecForObject<AddExchangeRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("forceUpdate", codecOptional(codecForBoolean())) - .build("AddExchangeRequest"); - -export interface UpdateExchangeEntryRequest { - exchangeBaseUrl: string; - force?: boolean; -} - -export const codecForUpdateExchangeEntryRequest = - (): Codec<UpdateExchangeEntryRequest> => - buildCodecForObject<UpdateExchangeEntryRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("force", codecOptional(codecForBoolean())) - .build("UpdateExchangeEntryRequest"); - -export interface GetExchangeResourcesRequest { - exchangeBaseUrl: string; -} - -export const codecForGetExchangeResourcesRequest = - (): Codec<GetExchangeResourcesRequest> => - buildCodecForObject<GetExchangeResourcesRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .build("GetExchangeResourcesRequest"); - -export interface GetExchangeResourcesResponse { - hasResources: boolean; -} - -export interface DeleteExchangeRequest { - exchangeBaseUrl: string; - purge?: boolean; -} - -export const codecForDeleteExchangeRequest = (): Codec<DeleteExchangeRequest> => - buildCodecForObject<DeleteExchangeRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("purge", codecOptional(codecForBoolean())) - .build("DeleteExchangeRequest"); - -export interface ForceExchangeUpdateRequest { - exchangeBaseUrl: string; -} - -export const codecForForceExchangeUpdateRequest = - (): Codec<AddExchangeRequest> => - buildCodecForObject<AddExchangeRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .build("AddExchangeRequest"); - -export interface GetExchangeTosRequest { - exchangeBaseUrl: string; - acceptedFormat?: string[]; - acceptLanguage?: string; -} - -export const codecForGetExchangeTosRequest = (): Codec<GetExchangeTosRequest> => - buildCodecForObject<GetExchangeTosRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("acceptedFormat", codecOptional(codecForList(codecForString()))) - .property("acceptLanguage", codecOptional(codecForString())) - .build("GetExchangeTosRequest"); - -export interface AcceptManualWithdrawalRequest { - exchangeBaseUrl: string; - amount: AmountString; - restrictAge?: number; - - /** - * Instead of generating a fresh, random reserve key pair, - * use the provided reserve private key. - * - * Use with caution. Usage of this field may be restricted - * to developer mode. - */ - forceReservePriv?: EddsaPrivateKeyString; -} - -export const codecForAcceptManualWithdrawalRequest = - (): Codec<AcceptManualWithdrawalRequest> => - buildCodecForObject<AcceptManualWithdrawalRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("amount", codecForAmountString()) - .property("restrictAge", codecOptional(codecForNumber())) - .property("forceReservePriv", codecOptional(codecForString())) - .build("AcceptManualWithdrawalRequest"); - -export interface GetWithdrawalDetailsForAmountRequest { - exchangeBaseUrl: string; - amount: AmountString; - restrictAge?: number; - - /** - * ID provided by the client to cancel the request. - * - * If the same request is made again with the same clientCancellationId, - * all previous requests are cancelled. - * - * The cancelled request will receive an error response with - * an error code that indicates the cancellation. - * - * The cancellation is best-effort, responses might still arrive. - */ - clientCancellationId?: string; -} - -export interface PrepareBankIntegratedWithdrawalRequest { - talerWithdrawUri: string; -} - -export const codecForPrepareBankIntegratedWithdrawalRequest = - (): Codec<PrepareBankIntegratedWithdrawalRequest> => - buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>() - .property("talerWithdrawUri", codecForString()) - .build("PrepareBankIntegratedWithdrawalRequest"); - -export interface PrepareBankIntegratedWithdrawalResponse { - transactionId: TransactionIdStr; - info: WithdrawUriInfoResponse; -} - -export interface ConfirmWithdrawalRequest { - transactionId: string; - exchangeBaseUrl: string; - amount: AmountString; - forcedDenomSel?: ForcedDenomSel; - restrictAge?: number; -} - -export const codecForConfirmWithdrawalRequestRequest = - (): Codec<ConfirmWithdrawalRequest> => - buildCodecForObject<ConfirmWithdrawalRequest>() - .property("transactionId", codecForString()) - .property("amount", codecForAmountString()) - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("forcedDenomSel", codecForAny()) - .property("restrictAge", codecOptional(codecForNumber())) - .build("ConfirmWithdrawalRequest"); - -export interface AcceptBankIntegratedWithdrawalRequest { - talerWithdrawUri: string; - exchangeBaseUrl: string; - forcedDenomSel?: ForcedDenomSel; - /** - * Amount to withdraw. - * If the bank's withdrawal operation uses a fixed amount, - * this field must either be left undefined or its value must match - * the amount from the withdrawal operation. - */ - amount?: AmountString; - restrictAge?: number; -} - -export const codecForAcceptBankIntegratedWithdrawalRequest = - (): Codec<AcceptBankIntegratedWithdrawalRequest> => - buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("talerWithdrawUri", codecForString()) - .property("forcedDenomSel", codecForAny()) - .property("amount", codecOptional(codecForAmountString())) - .property("restrictAge", codecOptional(codecForNumber())) - .build("AcceptBankIntegratedWithdrawalRequest"); - -export const codecForGetWithdrawalDetailsForAmountRequest = - (): Codec<GetWithdrawalDetailsForAmountRequest> => - buildCodecForObject<GetWithdrawalDetailsForAmountRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("amount", codecForAmountString()) - .property("restrictAge", codecOptional(codecForNumber())) - .property("clientCancellationId", codecOptional(codecForString())) - .build("GetWithdrawalDetailsForAmountRequest"); - -export interface AcceptExchangeTosRequest { - exchangeBaseUrl: string; -} - -export const codecForAcceptExchangeTosRequest = - (): Codec<AcceptExchangeTosRequest> => - buildCodecForObject<AcceptExchangeTosRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .build("AcceptExchangeTosRequest"); - -export interface ForgetExchangeTosRequest { - exchangeBaseUrl: string; -} - -export const codecForForgetExchangeTosRequest = - (): Codec<ForgetExchangeTosRequest> => - buildCodecForObject<ForgetExchangeTosRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .build("ForgetExchangeTosRequest"); - -export interface AcceptRefundRequest { - transactionId: TransactionIdStr; -} - -export const codecForApplyRefundRequest = (): Codec<AcceptRefundRequest> => - buildCodecForObject<AcceptRefundRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("AcceptRefundRequest"); - -export interface ApplyRefundFromPurchaseIdRequest { - purchaseId: string; -} - -export const codecForApplyRefundFromPurchaseIdRequest = - (): Codec<ApplyRefundFromPurchaseIdRequest> => - buildCodecForObject<ApplyRefundFromPurchaseIdRequest>() - .property("purchaseId", codecForString()) - .build("ApplyRefundFromPurchaseIdRequest"); - -export interface GetWithdrawalDetailsForUriRequest { - talerWithdrawUri: string; - /** - * @deprecated not used - */ - restrictAge?: number; -} - -export const codecForGetWithdrawalDetailsForUri = - (): Codec<GetWithdrawalDetailsForUriRequest> => - buildCodecForObject<GetWithdrawalDetailsForUriRequest>() - .property("talerWithdrawUri", codecForString()) - .property("restrictAge", codecOptional(codecForNumber())) - .build("GetWithdrawalDetailsForUriRequest"); - -export interface ListKnownBankAccountsRequest { - currency?: string; -} - -export const codecForListKnownBankAccounts = - (): Codec<ListKnownBankAccountsRequest> => - buildCodecForObject<ListKnownBankAccountsRequest>() - .property("currency", codecOptional(codecForString())) - .build("ListKnownBankAccountsRequest"); - -export interface AddKnownBankAccountsRequest { - payto: string; - alias: string; - currency: string; -} -export const codecForAddKnownBankAccounts = - (): Codec<AddKnownBankAccountsRequest> => - buildCodecForObject<AddKnownBankAccountsRequest>() - .property("payto", codecForString()) - .property("alias", codecForString()) - .property("currency", codecForString()) - .build("AddKnownBankAccountsRequest"); - -export interface ForgetKnownBankAccountsRequest { - payto: string; -} - -export const codecForForgetKnownBankAccounts = - (): Codec<ForgetKnownBankAccountsRequest> => - buildCodecForObject<ForgetKnownBankAccountsRequest>() - .property("payto", codecForString()) - .build("ForgetKnownBankAccountsRequest"); - -export interface AbortProposalRequest { - proposalId: string; -} - -export const codecForAbortProposalRequest = (): Codec<AbortProposalRequest> => - buildCodecForObject<AbortProposalRequest>() - .property("proposalId", codecForString()) - .build("AbortProposalRequest"); - -export interface GetContractTermsDetailsRequest { - // @deprecated use transaction id - proposalId?: string; - transactionId?: string; -} - -export const codecForGetContractTermsDetails = - (): Codec<GetContractTermsDetailsRequest> => - buildCodecForObject<GetContractTermsDetailsRequest>() - .property("proposalId", codecOptional(codecForString())) - .property("transactionId", codecOptional(codecForString())) - .build("GetContractTermsDetails"); - -export interface PreparePayRequest { - talerPayUri: string; -} - -export const codecForPreparePayRequest = (): Codec<PreparePayRequest> => - buildCodecForObject<PreparePayRequest>() - .property("talerPayUri", codecForString()) - .build("PreparePay"); - -export interface SharePaymentRequest { - merchantBaseUrl: string; - orderId: string; -} -export const codecForSharePaymentRequest = (): Codec<SharePaymentRequest> => - buildCodecForObject<SharePaymentRequest>() - .property("merchantBaseUrl", codecForCanonBaseUrl()) - .property("orderId", codecForString()) - .build("SharePaymentRequest"); - -export interface SharePaymentResult { - privatePayUri: string; -} -export const codecForSharePaymentResult = (): Codec<SharePaymentResult> => - buildCodecForObject<SharePaymentResult>() - .property("privatePayUri", codecForString()) - .build("SharePaymentResult"); - -export interface CheckPayTemplateRequest { - talerPayTemplateUri: string; -} - -export type CheckPayTemplateReponse = { - templateDetails: TalerMerchantApi.WalletTemplateDetails; - supportedCurrencies: string[]; -}; - -export const codecForCheckPayTemplateRequest = - (): Codec<CheckPayTemplateRequest> => - buildCodecForObject<CheckPayTemplateRequest>() - .property("talerPayTemplateUri", codecForString()) - .build("CheckPayTemplateRequest"); - -export interface PreparePayTemplateRequest { - talerPayTemplateUri: string; - templateParams?: TemplateParams; -} - -export const codecForPreparePayTemplateRequest = - (): Codec<PreparePayTemplateRequest> => - buildCodecForObject<PreparePayTemplateRequest>() - .property("talerPayTemplateUri", codecForString()) - .property("templateParams", codecForAny()) - .build("PreparePayTemplate"); - -export interface ConfirmPayRequest { - /** - * @deprecated use transactionId instead - */ - proposalId?: string; - transactionId?: TransactionIdStr; - sessionId?: string; - forcedCoinSel?: ForcedCoinSel; -} - -export const codecForConfirmPayRequest = (): Codec<ConfirmPayRequest> => - buildCodecForObject<ConfirmPayRequest>() - .property("proposalId", codecOptional(codecForString())) - .property("transactionId", codecOptional(codecForTransactionIdStr())) - .property("sessionId", codecOptional(codecForString())) - .property("forcedCoinSel", codecForAny()) - .build("ConfirmPay"); - -export interface CoreApiRequestEnvelope { - id: string; - operation: string; - args: unknown; -} - -export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; - -export type CoreApiMessageEnvelope = CoreApiResponse | CoreApiNotification; - -export interface CoreApiNotification { - type: "notification"; - payload: unknown; -} - -export interface CoreApiResponseSuccess { - // To distinguish the message from notifications - type: "response"; - operation: string; - id: string; - result: unknown; -} - -export interface CoreApiResponseError { - // To distinguish the message from notifications - type: "error"; - operation: string; - id: string; - error: TalerErrorDetail; -} - -export interface WithdrawTestBalanceRequest { - amount: AmountString; - /** - * Corebank API base URL. - */ - corebankApiBaseUrl: string; - exchangeBaseUrl: string; - forcedDenomSel?: ForcedDenomSel; -} - -/** - * Request to the crypto worker to make a sync signature. - */ -export interface MakeSyncSignatureRequest { - accountPriv: string; - oldHash: string | undefined; - newHash: string; -} - -/** - * Planchet for a coin during refresh. - */ -export interface RefreshPlanchetInfo { - /** - * Public key for the coin. - */ - coinPub: string; - - /** - * Private key for the coin. - */ - coinPriv: string; - - /** - * Blinded public key. - */ - coinEv: CoinEnvelope; - - coinEvHash: string; - - /** - * Blinding key used. - */ - blindingKey: string; - - maxAge: number; - ageCommitmentProof?: AgeCommitmentProof; -} - -/** - * Strategy for loading recovery information. - */ -export enum RecoveryMergeStrategy { - /** - * Keep the local wallet root key, import and take over providers. - */ - Ours = "ours", - - /** - * Migrate to the wallet root key from the recovery information. - */ - Theirs = "theirs", -} - -/** - * Load recovery information into the wallet. - */ -export interface RecoveryLoadRequest { - recovery: BackupRecovery; - strategy?: RecoveryMergeStrategy; -} - -export const codecForWithdrawTestBalance = - (): Codec<WithdrawTestBalanceRequest> => - buildCodecForObject<WithdrawTestBalanceRequest>() - .property("amount", codecForAmountString()) - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("forcedDenomSel", codecForAny()) - .property("corebankApiBaseUrl", codecForCanonBaseUrl()) - .build("WithdrawTestBalanceRequest"); - -export interface SetCoinSuspendedRequest { - coinPub: string; - suspended: boolean; -} - -export const codecForSetCoinSuspendedRequest = - (): Codec<SetCoinSuspendedRequest> => - buildCodecForObject<SetCoinSuspendedRequest>() - .property("coinPub", codecForString()) - .property("suspended", codecForBoolean()) - .build("SetCoinSuspendedRequest"); - -export interface RefreshCoinSpec { - coinPub: string; - amount?: AmountString; -} - -export const codecForRefreshCoinSpec = (): Codec<RefreshCoinSpec> => - buildCodecForObject<RefreshCoinSpec>() - .property("amount", codecForAmountString()) - .property("coinPub", codecForString()) - .build("ForceRefreshRequest"); - -export interface ForceRefreshRequest { - refreshCoinSpecs: RefreshCoinSpec[]; -} - -export const codecForForceRefreshRequest = (): Codec<ForceRefreshRequest> => - buildCodecForObject<ForceRefreshRequest>() - .property("refreshCoinSpecs", codecForList(codecForRefreshCoinSpec())) - .build("ForceRefreshRequest"); - -export interface PrepareRefundRequest { - talerRefundUri: string; -} - -export interface StartRefundQueryForUriResponse { - /** - * Transaction id of the *payment* where the refund query was started. - */ - transactionId: TransactionIdStr; -} - -export const codecForPrepareRefundRequest = (): Codec<PrepareRefundRequest> => - buildCodecForObject<PrepareRefundRequest>() - .property("talerRefundUri", codecForString()) - .build("PrepareRefundRequest"); - -export interface StartRefundQueryRequest { - transactionId: TransactionIdStr; -} - -export const codecForStartRefundQueryRequest = - (): Codec<StartRefundQueryRequest> => - buildCodecForObject<StartRefundQueryRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("StartRefundQueryRequest"); - -export interface FailTransactionRequest { - transactionId: TransactionIdStr; -} - -export const codecForFailTransactionRequest = - (): Codec<FailTransactionRequest> => - buildCodecForObject<FailTransactionRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("FailTransactionRequest"); - -export interface SuspendTransactionRequest { - transactionId: TransactionIdStr; -} - -export const codecForSuspendTransaction = - (): Codec<SuspendTransactionRequest> => - buildCodecForObject<AbortTransactionRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("SuspendTransactionRequest"); - -export interface ResumeTransactionRequest { - transactionId: TransactionIdStr; -} - -export const codecForResumeTransaction = (): Codec<ResumeTransactionRequest> => - buildCodecForObject<ResumeTransactionRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("ResumeTransactionRequest"); - -export interface AbortTransactionRequest { - transactionId: TransactionIdStr; -} - -export interface FailTransactionRequest { - transactionId: TransactionIdStr; -} - -export const codecForAbortTransaction = (): Codec<AbortTransactionRequest> => - buildCodecForObject<AbortTransactionRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("AbortTransactionRequest"); - -export interface DepositGroupFees { - coin: AmountString; - wire: AmountString; - refresh: AmountString; -} - -export interface CreateDepositGroupRequest { - /** - * Pre-allocated transaction ID. - * Allows clients to easily handle notifications - * that occur while the operation has been created but - * before the creation request has returned. - */ - transactionId?: TransactionIdStr; - depositPaytoUri: string; - amount: AmountString; -} - -export interface PrepareDepositRequest { - depositPaytoUri: string; - amount: AmountString; -} -export const codecForPrepareDepositRequest = (): Codec<PrepareDepositRequest> => - buildCodecForObject<PrepareDepositRequest>() - .property("amount", codecForAmountString()) - .property("depositPaytoUri", codecForString()) - .build("PrepareDepositRequest"); - -export interface PrepareDepositResponse { - totalDepositCost: AmountString; - effectiveDepositAmount: AmountString; - fees: DepositGroupFees; -} - -export const codecForCreateDepositGroupRequest = - (): Codec<CreateDepositGroupRequest> => - buildCodecForObject<CreateDepositGroupRequest>() - .property("amount", codecForAmountString()) - .property("depositPaytoUri", codecForString()) - .property("transactionId", codecOptional(codecForTransactionIdStr())) - .build("CreateDepositGroupRequest"); - -export interface CreateDepositGroupResponse { - depositGroupId: string; - transactionId: TransactionIdStr; -} - -export interface TxIdResponse { - transactionId: TransactionIdStr; -} - -export interface WithdrawUriInfoResponse { - operationId: string; - status: WithdrawalOperationStatus; - confirmTransferUrl?: string; - currency: string; - amount: AmountString | undefined; - editableAmount: boolean; - maxAmount: AmountString | undefined; - wireFee: AmountString | undefined; - defaultExchangeBaseUrl?: string; - editableExchange: boolean; - possibleExchanges: ExchangeListItem[]; -} - -export const codecForWithdrawUriInfoResponse = - (): Codec<WithdrawUriInfoResponse> => - buildCodecForObject<WithdrawUriInfoResponse>() - .property("operationId", codecForString()) - .property("confirmTransferUrl", codecOptional(codecForString())) - .property( - "status", - codecForEither( - codecForConstString("pending"), - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - ), - ) - .property("amount", codecOptional(codecForAmountString())) - .property("maxAmount", codecOptional(codecForAmountString())) - .property("wireFee", codecOptional(codecForAmountString())) - .property("currency", codecForString()) - .property("editableAmount", codecForBoolean()) - .property("editableExchange", codecForBoolean()) - .property("defaultExchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) - .property("possibleExchanges", codecForList(codecForExchangeListItem())) - .build("WithdrawUriInfoResponse"); - -export interface WalletCurrencyInfo { - trustedAuditors: { - currency: string; - auditorPub: string; - auditorBaseUrl: string; - }[]; - trustedExchanges: { - currency: string; - exchangeMasterPub: string; - exchangeBaseUrl: string; - }[]; -} - -export interface TestingListTasksForTransactionRequest { - transactionId: TransactionIdStr; -} - -export interface TestingListTasksForTransactionsResponse { - taskIdList: string[]; -} - -export const codecForTestingListTasksForTransactionRequest = - (): Codec<TestingListTasksForTransactionRequest> => - buildCodecForObject<TestingListTasksForTransactionRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("TestingListTasksForTransactionRequest"); - -export interface DeleteTransactionRequest { - transactionId: TransactionIdStr; -} - -export interface RetryTransactionRequest { - transactionId: TransactionIdStr; -} - -export const codecForDeleteTransactionRequest = - (): Codec<DeleteTransactionRequest> => - buildCodecForObject<DeleteTransactionRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("DeleteTransactionRequest"); - -export const codecForRetryTransactionRequest = - (): Codec<RetryTransactionRequest> => - buildCodecForObject<RetryTransactionRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("RetryTransactionRequest"); - -export interface SetWalletDeviceIdRequest { - /** - * New wallet device ID to set. - */ - walletDeviceId: string; -} - -export const codecForSetWalletDeviceIdRequest = - (): Codec<SetWalletDeviceIdRequest> => - buildCodecForObject<SetWalletDeviceIdRequest>() - .property("walletDeviceId", codecForString()) - .build("SetWalletDeviceIdRequest"); - -export interface WithdrawFakebankRequest { - amount: AmountString; - exchange: string; - bank: string; -} - -export enum AttentionPriority { - High = "high", - Medium = "medium", - Low = "low", -} - -export interface UserAttentionByIdRequest { - entityId: string; - type: AttentionType; -} - -export const codecForUserAttentionByIdRequest = - (): Codec<UserAttentionByIdRequest> => - buildCodecForObject<UserAttentionByIdRequest>() - .property("type", codecForAny()) - .property("entityId", codecForString()) - .build("UserAttentionByIdRequest"); - -export const codecForUserAttentionsRequest = (): Codec<UserAttentionsRequest> => - buildCodecForObject<UserAttentionsRequest>() - .property( - "priority", - codecOptional( - codecForEither( - codecForConstString(AttentionPriority.Low), - codecForConstString(AttentionPriority.Medium), - codecForConstString(AttentionPriority.High), - ), - ), - ) - .build("UserAttentionsRequest"); - -export interface UserAttentionsRequest { - priority?: AttentionPriority; -} - -export type AttentionInfo = - | AttentionKycWithdrawal - | AttentionBackupUnpaid - | AttentionBackupExpiresSoon - | AttentionMerchantRefund - | AttentionExchangeTosChanged - | AttentionExchangeKeyExpired - | AttentionExchangeDenominationExpired - | AttentionAuditorTosChanged - | AttentionAuditorKeyExpires - | AttentionAuditorDenominationExpires - | AttentionPullPaymentPaid - | AttentionPushPaymentReceived; - -export enum AttentionType { - KycWithdrawal = "kyc-withdrawal", - - BackupUnpaid = "backup-unpaid", - BackupExpiresSoon = "backup-expires-soon", - MerchantRefund = "merchant-refund", - - ExchangeTosChanged = "exchange-tos-changed", - ExchangeKeyExpired = "exchange-key-expired", - ExchangeKeyExpiresSoon = "exchange-key-expires-soon", - ExchangeDenominationsExpired = "exchange-denominations-expired", - ExchangeDenominationsExpiresSoon = "exchange-denominations-expires-soon", - - AuditorTosChanged = "auditor-tos-changed", - AuditorKeyExpires = "auditor-key-expires", - AuditorDenominationsExpires = "auditor-denominations-expires", - - PullPaymentPaid = "pull-payment-paid", - PushPaymentReceived = "push-payment-withdrawn", -} - -export const UserAttentionPriority: { - [type in AttentionType]: AttentionPriority; -} = { - "kyc-withdrawal": AttentionPriority.Medium, - - "backup-unpaid": AttentionPriority.High, - "backup-expires-soon": AttentionPriority.Medium, - "merchant-refund": AttentionPriority.Medium, - - "exchange-tos-changed": AttentionPriority.Medium, - - "exchange-key-expired": AttentionPriority.High, - "exchange-key-expires-soon": AttentionPriority.Medium, - "exchange-denominations-expired": AttentionPriority.High, - "exchange-denominations-expires-soon": AttentionPriority.Medium, - - "auditor-tos-changed": AttentionPriority.Medium, - "auditor-key-expires": AttentionPriority.Medium, - "auditor-denominations-expires": AttentionPriority.Medium, - - "pull-payment-paid": AttentionPriority.High, - "push-payment-withdrawn": AttentionPriority.High, -}; - -interface AttentionBackupExpiresSoon { - type: AttentionType.BackupExpiresSoon; - provider_base_url: string; -} -interface AttentionBackupUnpaid { - type: AttentionType.BackupUnpaid; - provider_base_url: string; - talerUri: string; -} - -interface AttentionMerchantRefund { - type: AttentionType.MerchantRefund; - transactionId: TransactionIdStr; -} - -interface AttentionKycWithdrawal { - type: AttentionType.KycWithdrawal; - transactionId: TransactionIdStr; -} - -interface AttentionExchangeTosChanged { - type: AttentionType.ExchangeTosChanged; - exchange_base_url: string; -} -interface AttentionExchangeKeyExpired { - type: AttentionType.ExchangeKeyExpired; - exchange_base_url: string; -} -interface AttentionExchangeDenominationExpired { - type: AttentionType.ExchangeDenominationsExpired; - exchange_base_url: string; -} -interface AttentionAuditorTosChanged { - type: AttentionType.AuditorTosChanged; - auditor_base_url: string; -} - -interface AttentionAuditorKeyExpires { - type: AttentionType.AuditorKeyExpires; - auditor_base_url: string; -} -interface AttentionAuditorDenominationExpires { - type: AttentionType.AuditorDenominationsExpires; - auditor_base_url: string; -} -interface AttentionPullPaymentPaid { - type: AttentionType.PullPaymentPaid; - transactionId: TransactionIdStr; -} - -interface AttentionPushPaymentReceived { - type: AttentionType.PushPaymentReceived; - transactionId: TransactionIdStr; -} - -export type UserAttentionUnreadList = Array<{ - info: AttentionInfo; - when: TalerPreciseTimestamp; - read: boolean; -}>; - -export interface UserAttentionsResponse { - pending: UserAttentionUnreadList; -} - -export interface UserAttentionsCountResponse { - total: number; -} - -export const codecForWithdrawFakebankRequest = - (): Codec<WithdrawFakebankRequest> => - buildCodecForObject<WithdrawFakebankRequest>() - .property("amount", codecForAmountString()) - .property("bank", codecForString()) - .property("exchange", codecForString()) - .build("WithdrawFakebankRequest"); - -export interface ActiveTask { - taskId: string; - transaction: TransactionIdStr | undefined; - firstTry: AbsoluteTime | undefined; - nextTry: AbsoluteTime | undefined; - retryCounter: number | undefined; - lastError: TalerErrorDetail | undefined; -} - -export interface GetActiveTasksResponse { - tasks: ActiveTask[]; -} - -export const codecForActiveTask = (): Codec<ActiveTask> => - buildCodecForObject<ActiveTask>() - .property("taskId", codecForString()) - .property("transaction", codecOptional(codecForTransactionIdStr())) - .property("retryCounter", codecOptional(codecForNumber())) - .property("firstTry", codecOptional(codecForAbsoluteTime)) - .property("nextTry", codecOptional(codecForAbsoluteTime)) - .property("lastError", codecOptional(codecForTalerErrorDetail())) - .build("ActiveTask"); - -export const codecForGetActiveTasks = (): Codec<GetActiveTasksResponse> => - buildCodecForObject<GetActiveTasksResponse>() - .property("tasks", codecForList(codecForActiveTask())) - .build("GetActiveTasks"); - -export interface ImportDbRequest { - dump: any; -} - -export const codecForImportDbRequest = (): Codec<ImportDbRequest> => - buildCodecForObject<ImportDbRequest>() - .property("dump", codecForAny()) - .build("ImportDbRequest"); - -export interface ForcedDenomSel { - denoms: { - value: AmountString; - count: number; - }[]; -} - -/** - * Forced coin selection for deposits/payments. - */ -export interface ForcedCoinSel { - coins: { - value: AmountString; - contribution: AmountString; - }[]; -} - -export interface TestPayResult { - /** - * Number of coins used for the payment. - */ - numCoins: number; -} - -export interface SelectedCoin { - denomPubHash: string; - coinPub: string; - contribution: AmountString; - exchangeBaseUrl: string; -} - -export interface SelectedProspectiveCoin { - denomPubHash: string; - contribution: AmountString; - exchangeBaseUrl: string; -} - -/** - * Result of selecting coins, contains the exchange, and selected - * coins with their denomination. - */ -export interface PayCoinSelection { - coins: SelectedCoin[]; - - /** - * How much of the wire fees is the customer paying? - */ - customerWireFees: AmountString; - - /** - * How much of the deposit fees is the customer paying? - */ - customerDepositFees: AmountString; -} - -export interface ProspectivePayCoinSelection { - prospectiveCoins: SelectedProspectiveCoin[]; - - /** - * How much of the wire fees is the customer paying? - */ - customerWireFees: AmountString; - - /** - * How much of the deposit fees is the customer paying? - */ - customerDepositFees: AmountString; -} - -export interface CheckPeerPushDebitRequest { - /** - * Preferred exchange to use for the p2p payment. - */ - exchangeBaseUrl?: string; - - /** - * Instructed amount. - * - * FIXME: Allow specifying the instructed amount type. - */ - amount: AmountString; -} - -export const codecForCheckPeerPushDebitRequest = - (): Codec<CheckPeerPushDebitRequest> => - buildCodecForObject<CheckPeerPushDebitRequest>() - .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) - .property("amount", codecForAmountString()) - .build("CheckPeerPushDebitRequest"); - -export interface CheckPeerPushDebitResponse { - amountRaw: AmountString; - amountEffective: AmountString; - exchangeBaseUrl: string; - /** - * Maximum expiration date, based on how close the coins - * used for the payment are to expiry. - * - * The value is based on when the wallet would typically - * automatically refresh the coins on its own, leaving enough - * time to get a refund for the push payment and refresh the - * coin. - */ - maxExpirationDate: TalerProtocolTimestamp; -} - -export interface InitiatePeerPushDebitRequest { - exchangeBaseUrl?: string; - partialContractTerms: PeerContractTerms; -} - -export interface InitiatePeerPushDebitResponse { - exchangeBaseUrl: string; - pursePub: string; - mergePriv: string; - contractPriv: string; - transactionId: TransactionIdStr; -} - -export const codecForInitiatePeerPushDebitRequest = - (): Codec<InitiatePeerPushDebitRequest> => - buildCodecForObject<InitiatePeerPushDebitRequest>() - .property("partialContractTerms", codecForPeerContractTerms()) - .build("InitiatePeerPushDebitRequest"); - -export interface PreparePeerPushCreditRequest { - talerUri: string; -} - -export interface PreparePeerPullDebitRequest { - talerUri: string; -} - -export interface PreparePeerPushCreditResponse { - contractTerms: PeerContractTerms; - amountRaw: AmountString; - amountEffective: AmountString; - - transactionId: TransactionIdStr; - - exchangeBaseUrl: string; - - /** - * @deprecated use transaction ID instead. - */ - peerPushCreditId: string; - - /** - * @deprecated - */ - amount: AmountString; -} - -export interface PreparePeerPullDebitResponse { - contractTerms: PeerContractTerms; - /** - * @deprecated Redundant field with bad name, will be removed soon. - */ - amount: AmountString; - - amountRaw: AmountString; - amountEffective: AmountString; - - peerPullDebitId: string; - - transactionId: TransactionIdStr; -} - -export const codecForPreparePeerPushCreditRequest = - (): Codec<PreparePeerPushCreditRequest> => - buildCodecForObject<PreparePeerPushCreditRequest>() - .property("talerUri", codecForString()) - .build("CheckPeerPushPaymentRequest"); - -export const codecForCheckPeerPullPaymentRequest = - (): Codec<PreparePeerPullDebitRequest> => - buildCodecForObject<PreparePeerPullDebitRequest>() - .property("talerUri", codecForString()) - .build("PreparePeerPullDebitRequest"); - -export interface ConfirmPeerPushCreditRequest { - transactionId: string; -} -export interface AcceptPeerPushPaymentResponse { - transactionId: TransactionIdStr; -} - -export interface AcceptPeerPullPaymentResponse { - transactionId: TransactionIdStr; -} - -export const codecForConfirmPeerPushPaymentRequest = - (): Codec<ConfirmPeerPushCreditRequest> => - buildCodecForObject<ConfirmPeerPushCreditRequest>() - .property("transactionId", codecForString()) - .build("ConfirmPeerPushCreditRequest"); - -export interface ConfirmPeerPullDebitRequest { - transactionId: TransactionIdStr; -} - -export interface ApplyDevExperimentRequest { - devExperimentUri: string; -} - -export const codecForApplyDevExperiment = - (): Codec<ApplyDevExperimentRequest> => - buildCodecForObject<ApplyDevExperimentRequest>() - .property("devExperimentUri", codecForString()) - .build("ApplyDevExperimentRequest"); - -export const codecForAcceptPeerPullPaymentRequest = - (): Codec<ConfirmPeerPullDebitRequest> => - buildCodecForObject<ConfirmPeerPullDebitRequest>() - .property("transactionId", codecForTransactionIdStr()) - .build("ConfirmPeerPullDebitRequest"); - -export interface CheckPeerPullCreditRequest { - exchangeBaseUrl?: string; - amount: AmountString; -} -export const codecForPreparePeerPullPaymentRequest = - (): Codec<CheckPeerPullCreditRequest> => - buildCodecForObject<CheckPeerPullCreditRequest>() - .property("amount", codecForAmountString()) - .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) - .build("CheckPeerPullCreditRequest"); - -export interface CheckPeerPullCreditResponse { - exchangeBaseUrl: string; - amountRaw: AmountString; - amountEffective: AmountString; - - /** - * Number of coins that will be used, - * can be used by the UI to warn if excessively large. - */ - numCoins: number; -} -export interface InitiatePeerPullCreditRequest { - exchangeBaseUrl?: string; - partialContractTerms: PeerContractTerms; -} - -export const codecForInitiatePeerPullPaymentRequest = - (): Codec<InitiatePeerPullCreditRequest> => - buildCodecForObject<InitiatePeerPullCreditRequest>() - .property("partialContractTerms", codecForPeerContractTerms()) - .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) - .build("InitiatePeerPullCreditRequest"); - -export interface InitiatePeerPullCreditResponse { - /** - * Taler URI for the other party to make the payment - * that was requested. - * - * @deprecated since it's not necessarily valid yet until the tx is in the right state - */ - talerUri: string; - - transactionId: TransactionIdStr; -} - -export interface CanonicalizeBaseUrlRequest { - url: string; -} - -export const codecForCanonicalizeBaseUrlRequest = - (): Codec<CanonicalizeBaseUrlRequest> => - buildCodecForObject<CanonicalizeBaseUrlRequest>() - .property("url", codecForString()) - .build("CanonicalizeBaseUrlRequest"); - -export interface CanonicalizeBaseUrlResponse { - url: string; -} - -export interface ValidateIbanRequest { - iban: string; -} - -export const codecForValidateIbanRequest = (): Codec<ValidateIbanRequest> => - buildCodecForObject<ValidateIbanRequest>() - .property("iban", codecForString()) - .build("ValidateIbanRequest"); - -export interface ValidateIbanResponse { - valid: boolean; -} - -export const codecForValidateIbanResponse = (): Codec<ValidateIbanResponse> => - buildCodecForObject<ValidateIbanResponse>() - .property("valid", codecForBoolean()) - .build("ValidateIbanResponse"); - -export type TransactionStateFilter = "nonfinal"; - -export interface TransactionRecordFilter { - onlyState?: TransactionStateFilter; - onlyCurrency?: string; -} - -export interface StoredBackupList { - storedBackups: { - name: string; - }[]; -} - -export interface CreateStoredBackupResponse { - name: string; -} - -export interface RecoverStoredBackupRequest { - name: string; -} - -export interface DeleteStoredBackupRequest { - name: string; -} - -export const codecForDeleteStoredBackupRequest = - (): Codec<DeleteStoredBackupRequest> => - buildCodecForObject<DeleteStoredBackupRequest>() - .property("name", codecForString()) - .build("DeleteStoredBackupRequest"); - -export const codecForRecoverStoredBackupRequest = - (): Codec<RecoverStoredBackupRequest> => - buildCodecForObject<RecoverStoredBackupRequest>() - .property("name", codecForString()) - .build("RecoverStoredBackupRequest"); - -export interface TestingSetTimetravelRequest { - offsetMs: number; -} - -export const codecForTestingSetTimetravelRequest = - (): Codec<TestingSetTimetravelRequest> => - buildCodecForObject<TestingSetTimetravelRequest>() - .property("offsetMs", codecForNumber()) - .build("TestingSetTimetravelRequest"); - -export interface AllowedAuditorInfo { - auditorBaseUrl: string; - auditorPub: string; -} - -export interface AllowedExchangeInfo { - exchangeBaseUrl: string; - exchangePub: string; -} - -/** - * Data extracted from the contract terms that is relevant for payment - * processing in the wallet. - */ -export interface WalletContractData { - /** - * Fulfillment URL, or the empty string if the order has no fulfillment URL. - * - * Stored as a non-nullable string as we use this field for IndexedDB indexing. - */ - fulfillmentUrl: string; - - contractTermsHash: string; - fulfillmentMessage?: string; - fulfillmentMessageI18n?: InternationalizedString; - merchantSig: string; - merchantPub: string; - merchant: MerchantInfo; - amount: AmountString; - orderId: string; - merchantBaseUrl: string; - summary: string; - summaryI18n: { [lang_tag: string]: string } | undefined; - autoRefund: TalerProtocolDuration | undefined; - payDeadline: TalerProtocolTimestamp; - refundDeadline: TalerProtocolTimestamp; - allowedExchanges: AllowedExchangeInfo[]; - timestamp: TalerProtocolTimestamp; - wireMethod: string; - wireInfoHash: string; - maxDepositFee: AmountString; - minimumAge?: number; -} - -export interface TestingWaitTransactionRequest { - transactionId: TransactionIdStr; - txState: TransactionState; -} - -export interface TestingGetReserveHistoryRequest { - reservePub: string; - exchangeBaseUrl: string; -} - -export const codecForTestingGetReserveHistoryRequest = - (): Codec<TestingGetReserveHistoryRequest> => - buildCodecForObject<TestingGetReserveHistoryRequest>() - .property("reservePub", codecForString()) - .property("exchangeBaseUrl", codecForString()) - .build("TestingGetReserveHistoryRequest"); - -export interface TestingGetDenomStatsRequest { - exchangeBaseUrl: string; -} - -export interface TestingGetDenomStatsResponse { - numKnown: number; - numOffered: number; - numLost: number; -} - -export const codecForTestingGetDenomStatsRequest = - (): Codec<TestingGetDenomStatsRequest> => - buildCodecForObject<TestingGetDenomStatsRequest>() - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .build("TestingGetDenomStatsRequest"); - -export interface WithdrawalExchangeAccountDetails { - /** - * Payto URI to credit the exchange. - * - * Depending on whether the (manual!) withdrawal is accepted or just - * being checked, this already includes the subject with the - * reserve public key. - */ - paytoUri: string; - - /** - * Status that indicates whether the account can be used - * by the user to send funds for a withdrawal. - * - * ok: account should be shown to the user - * error: account should not be shown to the user, UIs might render the error (in conversionError), - * especially in dev mode. - */ - status: "ok" | "error"; - - /** - * Transfer amount. Might be in a different currency than the requested - * amount for withdrawal. - * - * Absent if this is a conversion account and the conversion failed. - */ - transferAmount?: AmountString; - - /** - * Currency specification for the external currency. - * - * Only included if this account requires a currency conversion. - */ - currencySpecification?: CurrencySpecification; - - /** - * Further restrictions for sending money to the - * exchange. - */ - creditRestrictions?: AccountRestriction[]; - - /** - * Label given to the account or the account's bank by the exchange. - */ - bankLabel?: string; - - /* - * Display priority assigned to this bank account by the exchange. - */ - priority?: number; - - /** - * Error that happened when attempting to request the conversion rate. - */ - conversionError?: TalerErrorDetail; -} - -export interface PrepareWithdrawExchangeRequest { - /** - * A taler://withdraw-exchange URI. - */ - talerUri: string; -} - -export const codecForPrepareWithdrawExchangeRequest = - (): Codec<PrepareWithdrawExchangeRequest> => - buildCodecForObject<PrepareWithdrawExchangeRequest>() - .property("talerUri", codecForString()) - .build("PrepareWithdrawExchangeRequest"); - -export interface PrepareWithdrawExchangeResponse { - /** - * Base URL of the exchange that already existed - * or was ephemerally added as an exchange entry to - * the wallet. - */ - exchangeBaseUrl: string; - - /** - * Amount from the taler://withdraw-exchange URI. - * Only present if specified in the URI. - */ - amount?: AmountString; -} - -export interface ExchangeEntryState { - tosStatus: ExchangeTosStatus; - exchangeEntryStatus: ExchangeEntryStatus; - exchangeUpdateStatus: ExchangeUpdateStatus; -} - -export interface ListGlobalCurrencyAuditorsResponse { - auditors: { - currency: string; - auditorBaseUrl: string; - auditorPub: string; - }[]; -} - -export interface ListGlobalCurrencyExchangesResponse { - exchanges: { - currency: string; - exchangeBaseUrl: string; - exchangeMasterPub: string; - }[]; -} - -export interface AddGlobalCurrencyExchangeRequest { - currency: string; - exchangeBaseUrl: string; - exchangeMasterPub: string; -} - -export const codecForAddGlobalCurrencyExchangeRequest = - (): Codec<AddGlobalCurrencyExchangeRequest> => - buildCodecForObject<AddGlobalCurrencyExchangeRequest>() - .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("exchangeMasterPub", codecForString()) - .build("AddGlobalCurrencyExchangeRequest"); - -export interface RemoveGlobalCurrencyExchangeRequest { - currency: string; - exchangeBaseUrl: string; - exchangeMasterPub: string; -} - -export const codecForRemoveGlobalCurrencyExchangeRequest = - (): Codec<RemoveGlobalCurrencyExchangeRequest> => - buildCodecForObject<RemoveGlobalCurrencyExchangeRequest>() - .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForCanonBaseUrl()) - .property("exchangeMasterPub", codecForString()) - .build("RemoveGlobalCurrencyExchangeRequest"); - -export interface AddGlobalCurrencyAuditorRequest { - currency: string; - auditorBaseUrl: string; - auditorPub: string; -} - -export const codecForAddGlobalCurrencyAuditorRequest = - (): Codec<AddGlobalCurrencyAuditorRequest> => - buildCodecForObject<AddGlobalCurrencyAuditorRequest>() - .property("currency", codecForString()) - .property("auditorBaseUrl", codecForCanonBaseUrl()) - .property("auditorPub", codecForString()) - .build("AddGlobalCurrencyAuditorRequest"); - -export interface RemoveGlobalCurrencyAuditorRequest { - currency: string; - auditorBaseUrl: string; - auditorPub: string; -} - -export const codecForRemoveGlobalCurrencyAuditorRequest = - (): Codec<RemoveGlobalCurrencyAuditorRequest> => - buildCodecForObject<RemoveGlobalCurrencyAuditorRequest>() - .property("currency", codecForString()) - .property("auditorBaseUrl", codecForCanonBaseUrl()) - .property("auditorPub", codecForString()) - .build("RemoveGlobalCurrencyAuditorRequest"); - -/** - * Information about one provider. - * - * We don't store the account key here, - * as that's derived from the wallet root key. - */ -export interface ProviderInfo { - active: boolean; - syncProviderBaseUrl: string; - name: string; - terms?: BackupProviderTerms; - /** - * Last communication issue with the provider. - */ - lastError?: TalerErrorDetail; - lastSuccessfulBackupTimestamp?: TalerPreciseTimestamp; - lastAttemptedBackupTimestamp?: TalerPreciseTimestamp; - paymentProposalIds: string[]; - backupProblem?: BackupProblem; - paymentStatus: ProviderPaymentStatus; -} - -export interface BackupProviderTerms { - supportedProtocolVersion: string; - annualFee: AmountString; - storageLimitInMegabytes: number; -} - -export type BackupProblem = - | BackupUnreadableProblem - | BackupConflictingDeviceProblem; - -export interface BackupUnreadableProblem { - type: "backup-unreadable"; -} - -export interface BackupConflictingDeviceProblem { - type: "backup-conflicting-device"; - otherDeviceId: string; - myDeviceId: string; - backupTimestamp: AbsoluteTime; -} - -export type ProviderPaymentStatus = - | ProviderPaymentTermsChanged - | ProviderPaymentPaid - | ProviderPaymentInsufficientBalance - | ProviderPaymentUnpaid - | ProviderPaymentPending; - -export enum ProviderPaymentType { - Unpaid = "unpaid", - Pending = "pending", - InsufficientBalance = "insufficient-balance", - Paid = "paid", - TermsChanged = "terms-changed", -} - -export interface ProviderPaymentUnpaid { - type: ProviderPaymentType.Unpaid; -} - -export interface ProviderPaymentInsufficientBalance { - type: ProviderPaymentType.InsufficientBalance; - amount: AmountString; -} - -export interface ProviderPaymentPending { - type: ProviderPaymentType.Pending; - talerUri?: string; -} - -export interface ProviderPaymentPaid { - type: ProviderPaymentType.Paid; - paidUntil: AbsoluteTime; -} - -export interface ProviderPaymentTermsChanged { - type: ProviderPaymentType.TermsChanged; - paidUntil: AbsoluteTime; - oldTerms: BackupProviderTerms; - newTerms: BackupProviderTerms; -} - -// FIXME: Does not really belong here, move to sync API -export interface SyncTermsOfServiceResponse { - // maximum backup size supported - storage_limit_in_megabytes: number; - - // Fee for an account, per year. - annual_fee: AmountString; - - // protocol version supported by the server, - // for now always "0.0". - version: string; -} - -// FIXME: Does not really belong here, move to sync API -export const codecForSyncTermsOfServiceResponse = - (): Codec<SyncTermsOfServiceResponse> => - buildCodecForObject<SyncTermsOfServiceResponse>() - .property("storage_limit_in_megabytes", codecForNumber()) - .property("annual_fee", codecForAmountString()) - .property("version", codecForString()) - .build("SyncTermsOfServiceResponse"); - -export interface HintNetworkAvailabilityRequest { - isNetworkAvailable: boolean; -} - -export const codecForHintNetworkAvailabilityRequest = - (): Codec<HintNetworkAvailabilityRequest> => - buildCodecForObject<HintNetworkAvailabilityRequest>() - .property("isNetworkAvailable", codecForBoolean()) - .build("HintNetworkAvailabilityRequest"); - -export interface GetDepositWireTypesForCurrencyRequest { - currency: string; - /** - * Optional scope info to further restrict the result. - * Currency must match the currency field. - */ - scopeInfo?: ScopeInfo; -} - -export const codecForGetDepositWireTypesForCurrencyRequest = - (): Codec<GetDepositWireTypesForCurrencyRequest> => - buildCodecForObject<GetDepositWireTypesForCurrencyRequest>() - .property("currency", codecForString()) - .property("scopeInfo", codecOptional(codecForScopeInfo())) - .build("GetDepositWireTypesForCurrencyRequest"); - -/** - * Response with wire types that are supported for a deposit. - * - * In the future, we might surface more information here, such as debit restrictions - * by the exchange, which then can be shown by UIs to the user before they - * enter their payment information. - */ -export interface GetDepositWireTypesForCurrencyResponse { - wireTypes: string[]; -} - -export interface GetQrCodesForPaytoRequest { - paytoUri: string; -} - -export const codecForGetQrCodesForPaytoRequest = () => - buildCodecForObject<GetQrCodesForPaytoRequest>() - .property("paytoUri", codecForString()) - .build("GetQrCodesForPaytoRequest"); - -export interface GetQrCodesForPaytoResponse { - codes: QrCodeSpec[]; -} - -export type EmptyObject = Record<string, never>; - -export const codecForEmptyObject = (): Codec<EmptyObject> => - buildCodecForObject<EmptyObject>() - .build("EmptyObject"); -\ No newline at end of file diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -83,7 +83,7 @@ import { assertUnreachable, checkDbInvariant, checkLogicInvariant, - codeForBankWithdrawalOperationPostResponse, + codecForBankWithdrawalOperationPostResponse, codecForBankWithdrawalOperationStatus, codecForCashinConversionResponse, codecForConversionBankConfig, @@ -2573,7 +2573,7 @@ async function registerReserveWithBank( }); const status = await readSuccessResponseJsonOrThrow( httpResp, - codeForBankWithdrawalOperationPostResponse(), + codecForBankWithdrawalOperationPostResponse(), ); await ctx.transition({}, async (r) => {