/* This file is part of GNU Taler (C) 2019 GNUnet e.V. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ /** * Type and schema definitions and helpers for the core GNU Taler protocol. * * 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; /** * 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; 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() .property("cipher", codecForConstString(DenomKeyType.Rsa)) .property("blinded_rsa_signature", codecForString()) .build("RsaBlindedDenominationSignature"); export const codecForBlindedDenominationSignature = () => buildCodecForUnion() .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 => buildCodecForObject() .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() .property("cipher", codecForConstString(DenomKeyType.Rsa)) .property("rsa_public_key", codecForString()) .property("age_mask", codecForNumber()) .build("DenominationPubKey"); export const codecForCsDenominationPubKey = () => buildCodecForObject() .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr)) .property("cs_public_key", codecForString()) .property("age_mask", codecForNumber()) .build("CsDenominationPubKey"); export const codecForDenominationPubKey = () => buildCodecForUnion() .discriminateOn("cipher") .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey()) .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) .build("DenominationPubKey"); declare const __amount_str: unique symbol; export type AmountString = string & { [__amount_str]: true }; // 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 => buildCodecForObject() .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 => buildCodecForObject() .property("denom_pub_h", codecForString()) .property("auditor_sig", codecForString()) .build("AuditorDenomSig"); export const codecForAuditor = (): Codec => buildCodecForObject() .property("auditor_pub", codecForString()) .property("auditor_url", codecForString()) .property("denomination_keys", codecForList(codecForAuditorDenomSig())) .build("Auditor"); export const codecForExchangeHandle = (): Codec => buildCodecForObject() .property("master_pub", codecForString()) .property("url", codecForString()) .build("ExchangeHandle"); export const codecForAuditorHandle = (): Codec => buildCodecForObject() .property("name", codecForString()) .property("auditor_pub", codecForString()) .property("url", codecForString()) .build("AuditorHandle"); export const codecForLocation = (): Codec => buildCodecForObject() .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 => buildCodecForObject() .property("name", codecForString()) .property("address", codecOptional(codecForLocation())) .property("jurisdiction", codecOptional(codecForLocation())) .build("MerchantInfo"); export const codecForInternationalizedString = (): Codec => codecForMap(codecForString()); export const codecForMerchantContractTerms = (): Codec => buildCodecForObject() .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 => buildCodecForObject() .property("summary", codecForString()) .property("amount", codecForAmountString()) .property("purse_expiration", codecForTimestamp) .build("PeerContractTerms"); export const codecForMerchantRefundPermission = (): Codec => buildCodecForObject() .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 => buildCodecForObject() .property("blind_sig", codecForBlindedDenominationSignature()) .build("MerchantBlindSigWrapperV2"); export const codecForMerchantTipResponseV2 = (): Codec => buildCodecForObject() .property("blind_sigs", codecForList(codecForBlindSigWrapperV2())) .build("MerchantTipResponseV2"); export const codecForRecoup = (): Codec => buildCodecForObject() .property("h_denom_pub", codecForString()) .build("Recoup"); export const codecForExchangeSigningKey = (): Codec => buildCodecForObject() .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 => buildCodecForObject() .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 = codecForAny(); export const codecForExchangeKeysJson = (): Codec => buildCodecForObject() .property("base_url", codecForString()) .property("currency", codecForString()) .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 => buildCodecForObject() .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 => buildCodecForObject() .property("contract_terms", codecForAny()) .property("sig", codecForString()) .build("Proposal"); export const codecForCheckPaymentResponse = (): Codec => buildCodecForObject() .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 => buildCodecForObject() .property( "status", codecForEither( codecForConstString("selected"), codecForConstString("confirmed"), codecForConstString("aborted"), codecForConstString("pending"), ), ) .property("selection_done", codecForBoolean()) .property("transfer_done", codecForBoolean()) .property("aborted", codecForBoolean()) .property("amount", 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 => buildCodecForObject() .property("reward_amount", codecForString()) .property("exchange_url", codecForString()) .property("next_url", codecOptional(codecForString())) .property("expiration", codecForTimestamp) .build("TipPickupGetResponse"); export const codecForRecoupConfirmation = (): Codec => buildCodecForObject() .property("reserve_pub", codecOptional(codecForString())) .property("old_coin_pub", codecOptional(codecForString())) .build("RecoupConfirmation"); export const codecForWithdrawResponse = (): Codec => buildCodecForObject() .property("ev_sig", codecForBlindedDenominationSignature()) .build("WithdrawResponse"); export const codecForExchangeWithdrawBatchResponse = (): Codec => buildCodecForObject() .property("ev_sigs", codecForList(codecForWithdrawResponse())) .build("WithdrawBatchResponse"); export const codecForMerchantPayResponse = (): Codec => buildCodecForObject() .property("sig", codecForString()) .property("pos_confirmation", codecOptional(codecForString())) .build("MerchantPayResponse"); export const codecForExchangeMeltResponse = (): Codec => buildCodecForObject() .property("exchange_pub", codecForString()) .property("exchange_sig", codecForString()) .property("noreveal_index", codecForNumber()) .property("refresh_base_url", codecOptional(codecForString())) .build("ExchangeMeltResponse"); export const codecForExchangeRevealItem = (): Codec => buildCodecForObject() .property("ev_sig", codecForBlindedDenominationSignature()) .build("ExchangeRevealItem"); export const codecForExchangeRevealResponse = (): Codec => buildCodecForObject() .property("ev_sigs", codecForList(codecForExchangeRevealItem())) .build("ExchangeRevealResponse"); export const codecForMerchantOrderStatusPaid = (): Codec => buildCodecForObject() .property("refund_amount", codecForAmountString()) .property("refund_taken", codecForAmountString()) .property("refund_pending", codecForBoolean()) .property("refunded", codecForBoolean()) .build("MerchantOrderStatusPaid"); export const codecForMerchantOrderStatusUnpaid = (): Codec => buildCodecForObject() .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 => buildCodecForObject() .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 => buildCodecForObject() .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 ` 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 ` 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 => buildCodecForObject() .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 => buildCodecForObject() .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 => buildCodecForObject() .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 => buildCodecForObject() .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 `, 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_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 => buildCodecForObject() .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 => buildCodecForObject() .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 => buildCodecForObject() .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 => buildCodecForObject() .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() .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() .property("balance", codecForAmountString()) .property("h_denom_pub", codecForString()) .property("history", codecForAny()) .build("CoinHistoryResponse");