taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 65324a5c030e997115e2b240dcdc5560052c1117
parent 3e6435b613986621b7b2bf20f448b87d152705d2
Author: Florian Dold <florian@dold.me>
Date:   Wed, 30 Apr 2025 22:25:58 +0200

update supported exchange version, have version only in once place

Diffstat:
Apackages/taler-util/src/http-client/exchange-client.ts | 1390+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpackages/taler-util/src/http-client/exchange.ts | 1389-------------------------------------------------------------------------------
Mpackages/taler-util/src/index.ts | 6+++---
Mpackages/taler-wallet-core/src/versions.ts | 5++++-
Mpackages/web-util/src/context/exchange-api.ts | 19++++++++++++++-----
5 files changed, 1411 insertions(+), 1398 deletions(-)

diff --git a/packages/taler-util/src/http-client/exchange-client.ts b/packages/taler-util/src/http-client/exchange-client.ts @@ -0,0 +1,1390 @@ +/* + This file is part of GNU Taler + (C) 2022-2025 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 { Codec, codecForAny } from "../codec.js"; +import { + HttpRequestLibrary, + HttpRequestOptions, + HttpResponse, + readSuccessResponseJsonOrThrow, + readTalerErrorResponse, +} from "../http-common.js"; +import { HttpStatusCode } from "../http-status-codes.js"; +import { createPlatformHttpLib } from "../http.js"; +import { LibtoolVersion } from "../libtool-version.js"; +import { + FailCasesByMethod, + OperationAlternative, + OperationFail, + OperationOk, + ResultByMethod, + opEmptySuccess, + opFixedSuccess, + opKnownAlternativeFailure, + opKnownHttpFailure, + opSuccessFromHttp, + opUnknownFailure, + opUnknownHttpFailure, +} from "../operation.js"; +import { EddsaPrivP, encodeCrock } from "../taler-crypto.js"; +import { + AccessToken, + AmountString, + OfficerAccount, + PaginationParams, + ReserveAccount, + codecForTalerCommonConfigResponse, +} from "../types-taler-common.js"; +import { + AccountKycStatus, + AmlDecisionRequest, + BatchWithdrawResponse, + ExchangeGetContractResponse, + ExchangeKycUploadFormRequest, + ExchangeLegacyBatchWithdrawRequest, + ExchangeMergeConflictResponse, + ExchangeMergeSuccessResponse, + ExchangePurseDeposits, + ExchangePurseMergeRequest, + ExchangePurseStatus, + ExchangeReservePurseRequest, + ExchangeVersionResponse, + KycRequirementInformationId, + LegitimizationNeededResponse, + PurseConflict, + PurseConflictPartial, + WalletKycRequest, + codecForAccountKycStatus, + codecForAmlDecisionsResponse, + codecForAmlKycAttributes, + codecForAmlWalletKycCheckResponse, + codecForAvailableMeasureSummary, + codecForEventCounter, + codecForExchangeConfig, + codecForExchangeGetContractResponse, + codecForExchangeKeysResponse, + codecForExchangeMergeConflictResponse, + codecForExchangeMergeSuccessResponse, + codecForExchangePurseStatus, + codecForExchangeTransferList, + codecForKycProcessClientInformation, + codecForKycProcessStartInformation, + codecForLegitimizationNeededResponse, + codecForPurseConflict, + codecForPurseConflictPartial, +} from "../types-taler-exchange.js"; +import { CacheEvictor, addPaginationParams, nullEvictor } from "./utils.js"; + +import { TalerError } from "../errors.js"; +import { + AmountJson, + Amounts, + CancellationToken, + LongpollQueue, + signAmlDecision, + signAmlQuery, + signKycAuth, + signWalletAccountSetup, +} from "../index.js"; +import { TalerErrorCode } from "../taler-error-codes.js"; +import { AbsoluteTime } from "../time.js"; +import { codecForEmptyObject } from "../types-taler-wallet.js"; + +export type TalerExchangeResultByMethod< + prop extends keyof TalerExchangeHttpClient, +> = ResultByMethod<TalerExchangeHttpClient, prop>; +export type TalerExchangeErrorsByMethod< + prop extends keyof TalerExchangeHttpClient, +> = FailCasesByMethod<TalerExchangeHttpClient, prop>; + +export enum TalerExchangeCacheEviction { + UPLOAD_KYC_FORM, + MAKE_AML_DECISION, +} + +declare const __pubId: unique symbol; +export type ReservePub = string & { [__pubId]: true }; + +/** + * Client library for the GNU Taler exchange service. + */ +export class TalerExchangeHttpClient { + public static readonly SUPPORTED_EXCHANGE_PROTOCOL_VERSION = "27:0:2"; + private httpLib: HttpRequestLibrary; + private cacheEvictor: CacheEvictor<TalerExchangeCacheEviction>; + private preventCompression: boolean; + private cancelationToken: CancellationToken; + private longPollQueue: LongpollQueue; + + constructor( + readonly baseUrl: string, + params: { + httpClient?: HttpRequestLibrary; + cacheEvictor?: CacheEvictor<TalerExchangeCacheEviction>; + preventCompression?: boolean; + cancelationToken?: CancellationToken; + longPollQueue?: LongpollQueue; + }, + ) { + this.httpLib = params.httpClient ?? createPlatformHttpLib(); + this.cacheEvictor = params.cacheEvictor ?? nullEvictor; + this.preventCompression = !!params.preventCompression; + this.cancelationToken = + params.cancelationToken ?? CancellationToken.CONTINUE; + this.longPollQueue = params.longPollQueue ?? new LongpollQueue(); + } + + isCompatible(version: string): boolean { + const compare = LibtoolVersion.compare( + TalerExchangeHttpClient.SUPPORTED_EXCHANGE_PROTOCOL_VERSION, + version, + ); + return compare?.compatible ?? false; + } + + private async fetch( + url_or_path: URL | string, + opts: HttpRequestOptions = {}, + longpoll: boolean = false, + ): Promise<HttpResponse> { + const url = + typeof url_or_path == "string" + ? new URL(url_or_path, this.baseUrl) + : url_or_path; + if (longpoll || url.searchParams.has("timeout_ms")) { + return this.longPollQueue.run( + url, + this.cancelationToken, + async (timeoutMs) => { + url.searchParams.set("timeout_ms", String(timeoutMs)); + return this.httpLib.fetch(url.href, { + cancellationToken: this.cancelationToken, + ...opts, + }); + }, + ); + } else { + return this.httpLib.fetch(url.href, { + cancellationToken: this.cancelationToken, + ...opts, + }); + } + } + + // TERMS + + /** + * https://docs.taler.net/core/api-exchange.html#get--seed + * + */ + /** + * https://docs.taler.net/core/api-exchange.html#get--seed + * + */ + + // EXCHANGE INFORMATION + + /** + * https://docs.taler.net/core/api-exchange.html#get--seed + * + */ + async getSeed(): Promise< + | OperationOk<Uint8Array<ArrayBuffer>> + | OperationFail<HttpStatusCode.NotFound> + > { + const resp = await this.fetch("seed"); + switch (resp.status) { + case HttpStatusCode.Ok: + const buffer = await resp.bytes(); + const uintar = new Uint8Array(buffer); + return opFixedSuccess(uintar); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + /** + * https://docs.taler.net/core/api-exchange.html#get--config + * + */ + async getConfig(): Promise< + | OperationFail<HttpStatusCode.NotFound> + | OperationOk<ExchangeVersionResponse> + > { + const resp = await this.fetch("config"); + switch (resp.status) { + case HttpStatusCode.Ok: { + const minBody = await readSuccessResponseJsonOrThrow( + resp, + codecForTalerCommonConfigResponse(), + ); + const expectedName = "taler-exchange"; + if (minBody.name !== expectedName) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`, + }); + } + if (!this.isCompatible(minBody.version)) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unsupported protocol version, client supports ${TalerExchangeHttpClient.SUPPORTED_EXCHANGE_PROTOCOL_VERSION}, server supports ${minBody.version}`, + }); + } + // Now that we've checked the basic body, re-parse the full response. + const body = await readSuccessResponseJsonOrThrow( + resp, + codecForExchangeConfig(), + ); + return opFixedSuccess(body); + } + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-merchant.html#get--config + * + * PARTIALLY IMPLEMENTED!! + */ + async getKeys() { + const resp = await this.fetch("keys"); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangeKeysResponse()); + default: + return opUnknownHttpFailure(resp); + } + } + + // + // MANAGEMENT + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--management-keys + * + */ + async getFutureKeys(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-keys + * + */ + async signFutureKeys(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-denominations-$H_DENOM_PUB-revoke + * + */ + async revokeFutureDenominationKeys(): Promise<never> { + throw Error("not yet implemented"); + } + /** + * https://docs.taler.net/core/api-exchange.html#post--management-signkeys-$EXCHANGE_PUB-revoke + * + */ + async revokeFutureSigningKeys(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-auditors + * + */ + async enableAuditor(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-auditors-$AUDITOR_PUB-disable + * + */ + async disableAuditor(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-wire-fee + * + */ + async configWireFee(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-global-fees + * + */ + async configGlobalFees(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-wire + * + */ + async enableWireMethod(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-wire-disable + * + */ + async disableWireMethod(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-drain + * + */ + async drainProfits(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-aml-officers + * + */ + async updateOfficer(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--management-partners + * + */ + async enablePartner(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // AUDITOR + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--auditors-$AUDITOR_PUB-$H_DENOM_PUB + * + */ + async addAuditor(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // WITHDRAWAL + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB + * + */ + async getReserveInfo(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--csr-withdraw + * + */ + async prepareCsrWithdawal(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-batch-withdraw + * + */ + async withdraw(rid: ReservePub, body: ExchangeLegacyBatchWithdrawRequest) { + const resp = await this.fetch(`reserves/${rid}/batch-withdraw`, { + method: "POST", + body, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp( + resp, + codecForAny() as Codec<BatchWithdrawResponse>, + ); + case HttpStatusCode.Forbidden: + case HttpStatusCode.BadRequest: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + case HttpStatusCode.Gone: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForLegitimizationNeededResponse(), + ); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction + * + */ + async withdrawWithAge(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--age-withdraw-$ACH-reveal + * + */ + async revealCoinsForAge(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // RESERVE HISTORY + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB-history + * + */ + async getResverveHistory(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // COIN HISTORY + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-history + * + */ + async getCoinHistory(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // DEPOSIT + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--batch-deposit + * + */ + async deposit(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // REFRESH + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--csr-melt + * + */ + async prepareCsrMelt(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-melt + * + */ + async meltCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-link + * + */ + async linkCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // RECOUP + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup + * + */ + async recoupReserveCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup-refresh + * + */ + async recoupRefreshCoin(): Promise<never> { + throw Error("not yet implemented"); + } + + // WIRE TRANSFER + + /** + * https://docs.taler.net/core/api-exchange.html#get--transfers-$WTID + * + */ + async getWireTransferInfo(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--deposits-$H_WIRE-$MERCHANT_PUB-$H_CONTRACT_TERMS-$COIN_PUB + * + */ + async getWireTransferIdForDeposit(): Promise<never> { + throw Error("not yet implemented"); + } + + // REFUND + + /** + * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-refund + * + */ + async refund(): Promise<never> { + throw Error("not yet implemented"); + } + + // WALLET TO WALLET + + /** + * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-merge + * + */ + async getPurseStatusAtMerge( + pursePub: string, + longpoll: boolean = false, + ): Promise< + | OperationOk<ExchangePurseStatus> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Gone> + > { + const resp = await this.fetch(`purses/${pursePub}/merge`, {}, longpoll); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangePurseStatus()); + case HttpStatusCode.Gone: + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-deposit + * + */ + async getPurseStatusAtDeposit( + pursePub: string, + longpoll: boolean = false, + ): Promise< + | OperationOk<ExchangePurseStatus> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Gone> + > { + const resp = await this.fetch(`purses/${pursePub}/deposit`, {}, longpoll); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangePurseStatus()); + case HttpStatusCode.Gone: + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-create + * + */ + async createPurseFromDeposit( + pursePub: string, + body: any, // FIXME + ): Promise< + | OperationOk<void> + | OperationFail<HttpStatusCode.Forbidden> + | OperationFail<HttpStatusCode.NotFound> + | OperationAlternative<HttpStatusCode.Conflict, PurseConflict> + | OperationFail<HttpStatusCode.TooEarly> + > { + const resp = await this.fetch(`purses/${pursePub}/create`, { + method: "POST", + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + // FIXME: parse PurseCreateSuccessResponse + return opSuccessFromHttp(resp, codecForAny()); + case HttpStatusCode.Conflict: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForPurseConflict(), + ); + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.TooEarly: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#delete--purses-$PURSE_PUB + * + */ + async deletePurse( + pursePub: string, + purseSig: string, + ): Promise< + | OperationOk<void> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Conflict> + | OperationFail<HttpStatusCode.Forbidden> + > { + const resp = await this.fetch(`purses/${pursePub}`, { + method: "DELETE", + headers: { + "taler-purse-signature": purseSig, + }, + }); + switch (resp.status) { + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * POST /purses/$PURSE_PUB/merge + * + * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge + */ + async postPurseMerge( + pursePub: string, + body: ExchangePurseMergeRequest, + ): Promise< + | OperationOk<ExchangeMergeSuccessResponse> + | OperationAlternative< + HttpStatusCode.UnavailableForLegalReasons, + LegitimizationNeededResponse + > + | OperationAlternative< + HttpStatusCode.Conflict, + ExchangeMergeConflictResponse + > + | OperationFail<HttpStatusCode.Forbidden> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Gone> + > { + const resp = await this.fetch(`purses/${pursePub}/merge`, { + method: "POST", + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangeMergeSuccessResponse()); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForLegitimizationNeededResponse(), + ); + case HttpStatusCode.Conflict: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForExchangeMergeConflictResponse(), + ); + case HttpStatusCode.Gone: + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-purse + * + */ + async createPurseFromReserve( + pursePub: string, + body: ExchangeReservePurseRequest, + ): Promise< + | OperationOk<void> + | OperationFail<HttpStatusCode.PaymentRequired> + | OperationFail<HttpStatusCode.Forbidden> + | OperationFail<HttpStatusCode.NotFound> + | OperationAlternative<HttpStatusCode.Conflict, PurseConflictPartial> + | OperationAlternative< + HttpStatusCode.UnavailableForLegalReasons, + LegitimizationNeededResponse + > + > { + const resp = await this.fetch(`reserves/${pursePub}/purse`, { + method: "POST", + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + // FIXME: parse PurseCreateSuccessResponse + return opSuccessFromHttp(resp, codecForAny()); + case HttpStatusCode.PaymentRequired: + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForPurseConflictPartial(), + ); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForLegitimizationNeededResponse(), + ); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--contracts-$CONTRACT_PUB + * + */ + async getContract( + pursePub: string, + ): Promise< + | OperationOk<ExchangeGetContractResponse> + | OperationFail<HttpStatusCode.NotFound> + > { + const resp = await this.fetch(`contracts/${pursePub}`); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangeGetContractResponse()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-deposit + * + */ + async depositIntoPurse( + pursePub: string, + body: ExchangePurseDeposits, + ): Promise< + | OperationOk<void> + | OperationAlternative<HttpStatusCode.Conflict, PurseConflict> + | OperationFail<HttpStatusCode.Forbidden> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Gone> + > { + const resp = await this.fetch(`purses/${pursePub}/deposit`, { + method: "POST", + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + // FIXME: parse PurseDepositSuccessResponse + return opSuccessFromHttp(resp, codecForAny()); + case HttpStatusCode.Conflict: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForPurseConflict(), + ); + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Gone: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + // WADS + + /** + * https://docs.taler.net/core/api-exchange.html#get--wads-$WAD_ID + * + */ + async getWadInfo(): Promise<never> { + throw Error("not yet implemented"); + } + + // + // KYC + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet + * + */ + async notifyKycBalanceLimit(account: ReserveAccount, balance: AmountString) { + const body: WalletKycRequest = { + balance, + reserve_pub: account.id, + reserve_sig: encodeCrock( + signWalletAccountSetup(account.signingKey, balance), + ), + }; + const resp = await this.fetch(`kyc-wallet`, { + method: "POST", + body, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAmlWalletKycCheckResponse()); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForLegitimizationNeededResponse(), + ); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--kyc-check-$H_NORMALIZED_PAYTO + * + */ + async checkKycStatus( + signingKey: EddsaPrivP | string, + paytoHash: string, + longpoll: boolean = false, + params: { + awaitAuth?: boolean; + } = {}, + ): Promise< + | OperationOk<void> + | OperationAlternative<HttpStatusCode.Ok, AccountKycStatus> + | OperationAlternative<HttpStatusCode.Accepted, AccountKycStatus> + | OperationFail<HttpStatusCode.Forbidden> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Conflict> + > { + const url = new URL(`kyc-check/${paytoHash}`, this.baseUrl); + if (params.awaitAuth !== undefined) { + url.searchParams.set("await_auth", params.awaitAuth ? "YES" : "NO"); + } + + const signature = + typeof signingKey === "string" + ? signingKey + : encodeCrock(signKycAuth(signingKey)); + + const resp = await this.fetch( + url, + { + headers: { + "Account-Owner-Signature": signature, + }, + }, + longpoll, + ); + + switch (resp.status) { + case HttpStatusCode.Ok: + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForAccountKycStatus(), + ); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--kyc-info-$ACCESS_TOKEN + * + */ + async checkKycInfo( + token: AccessToken, + known: KycRequirementInformationId[], + longpoll: boolean = false, + ) { + const resp = await this.fetch( + `kyc-info/${token}`, + { + method: "GET", + headers: { + "If-None-Match": known.length ? known.join(",") : undefined, + }, + }, + longpoll, + ); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForKycProcessClientInformation()); + case HttpStatusCode.Accepted: + case HttpStatusCode.NoContent: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForEmptyObject(), + ); + case HttpStatusCode.NotModified: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--kyc-upload-$ID + * + */ + async uploadKycForm<T extends ExchangeKycUploadFormRequest>( + requirement: KycRequirementInformationId, + body: T, + ) { + const resp = await this.fetch(`kyc-upload/${requirement}`, { + method: "POST", + body, + compress: this.preventCompression ? undefined : "deflate", + }); + switch (resp.status) { + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess( + TalerExchangeCacheEviction.UPLOAD_KYC_FORM, + ); + return opEmptySuccess(resp); + } + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + case HttpStatusCode.PayloadTooLarge: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--kyc-start-$ID + * + */ + async startExternalKycProcess( + requirement: KycRequirementInformationId, + body: object = {}, + ) { + const resp = await this.fetch(`kyc-start/${requirement}`, { + method: "POST", + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForKycProcessStartInformation()); + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + case HttpStatusCode.PayloadTooLarge: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--kyc-proof-$PROVIDER_NAME?state=$H_PAYTO + * + */ + async completeExternalKycProcess( + provider: string, + state: string, + code: string, + ) { + const resp = await this.fetch( + `kyc-proof/${provider}?state=${state}&code=${code}`, + { + method: "GET", + redirect: "manual", + }, + ); + + switch (resp.status) { + case HttpStatusCode.SeeOther: + return opEmptySuccess(resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + // + // AML operations + // + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures + * + */ + async getAmlMesasures(auth: OfficerAccount) { + const resp = await this.fetch(`aml/${auth.id}/measures`, { + method: "GET", + headers: { + "Taler-AML-Officer-Signature": encodeCrock( + signAmlQuery(auth.signingKey), + ), + }, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAvailableMeasureSummary()); + case HttpStatusCode.Conflict: + case HttpStatusCode.NotFound: + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures + * + */ + async getAmlKycStatistics( + auth: OfficerAccount, + name: string, + filter: { + since?: AbsoluteTime; + until?: AbsoluteTime; + } = {}, + ) { + const url = new URL(`aml/${auth.id}/kyc-statistics/${name}`, this.baseUrl); + + if (filter.since !== undefined && filter.since.t_ms !== "never") { + url.searchParams.set("start_date", String(filter.since.t_ms)); + } + if (filter.until !== undefined && filter.until.t_ms !== "never") { + url.searchParams.set("end_date", String(filter.until.t_ms)); + } + + const resp = await this.fetch(url, { + method: "GET", + headers: { + "Taler-AML-Officer-Signature": encodeCrock( + signAmlQuery(auth.signingKey), + ), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForEventCounter()); + case HttpStatusCode.Conflict: + case HttpStatusCode.NotFound: + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions + * + */ + async getAmlDecisions( + auth: OfficerAccount, + params: PaginationParams & { + account?: string; + active?: boolean; + investigation?: boolean; + } = {}, + ) { + const url = new URL(`aml/${auth.id}/decisions`, this.baseUrl); + + addPaginationParams(url, params); + if (params.account !== undefined) { + url.searchParams.set("h_payto", params.account); + } + if (params.active !== undefined) { + url.searchParams.set("active", params.active ? "YES" : "NO"); + } + if (params.investigation !== undefined) { + url.searchParams.set( + "investigation", + params.investigation ? "YES" : "NO", + ); + } + + const resp = await this.fetch(url, { + headers: { + "Taler-AML-Officer-Signature": encodeCrock( + signAmlQuery(auth.signingKey), + ), + }, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAmlDecisionsResponse()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ records: [] }); + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO + * + */ + async getAmlAttributesForAccount( + auth: OfficerAccount, + account: string, + params: PaginationParams = {}, + ) { + const url = new URL(`aml/${auth.id}/attributes/${account}`, this.baseUrl); + + addPaginationParams(url, params); + const resp = await this.fetch(url, { + headers: { + "Taler-AML-Officer-Signature": encodeCrock( + signAmlQuery(auth.signingKey), + ), + }, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAmlKycAttributes()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ details: [] }); + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision + * + */ + async makeAmlDesicion( + auth: OfficerAccount, + decision: Omit<AmlDecisionRequest, "officer_sig">, + ) { + const body: AmlDecisionRequest = { + officer_sig: encodeCrock(signAmlDecision(auth.signingKey, decision)), + ...decision, + }; + const resp = await this.fetch(`aml/${auth.id}/decision`, { + method: "POST", + headers: { + "Taler-AML-Officer-Signature": encodeCrock( + signAmlQuery(auth.signingKey), + ), + }, + body, + compress: this.preventCompression ? undefined : "deflate", + }); + + switch (resp.status) { + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess( + TalerExchangeCacheEviction.MAKE_AML_DECISION, + ); + return opEmptySuccess(resp); + } + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-transfers-credit + * + */ + async getTransfersCredit( + auth: OfficerAccount, + params: PaginationParams & { threshold?: AmountJson } = {}, + ) { + const url = new URL(`aml/${auth.id}/transfers-credit`, this.baseUrl); + + addPaginationParams(url, params); + + if (params.threshold) { + url.searchParams.set("threshold", Amounts.stringify(params.threshold)); + } + + const resp = await this.fetch(url, { + headers: { + "Taler-AML-Officer-Signature": encodeCrock( + signAmlQuery(auth.signingKey), + ), + }, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangeTransferList()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ transfers: [] }); + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-transfers-debit + * + */ + async getTransfersDebit( + auth: OfficerAccount, + params: PaginationParams & { threshold?: AmountJson } = {}, + ) { + const url = new URL(`aml/${auth.id}/transfers-debit`, this.baseUrl); + + addPaginationParams(url, params); + + if (params.threshold) { + url.searchParams.set("threshold", Amounts.stringify(params.threshold)); + } + + const resp = await this.fetch(url, { + headers: { + "Taler-AML-Officer-Signature": encodeCrock( + signAmlQuery(auth.signingKey), + ), + }, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForExchangeTransferList()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ transfers: [] }); + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + // RESERVE control + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-open + * + */ + async reserveOpen(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--reserves-attest-$RESERVE_PUB + * + */ + async getReserveAttributes(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-attest-$RESERVE_PUB + * + */ + async signReserveAttributes(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-close + * + */ + async closeReserve(): Promise<never> { + throw Error("not yet implemented"); + } + + /** + * https://docs.taler.net/core/api-exchange.html#delete--reserves-$RESERVE_PUB + * + */ + async deleteReserve(): Promise<never> { + throw Error("not yet implemented"); + } +} diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts @@ -1,1389 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022-2025 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 { Codec, codecForAny } from "../codec.js"; -import { - HttpRequestLibrary, - HttpRequestOptions, - HttpResponse, - readSuccessResponseJsonOrThrow, - readTalerErrorResponse, -} from "../http-common.js"; -import { HttpStatusCode } from "../http-status-codes.js"; -import { createPlatformHttpLib } from "../http.js"; -import { LibtoolVersion } from "../libtool-version.js"; -import { - FailCasesByMethod, - OperationAlternative, - OperationFail, - OperationOk, - ResultByMethod, - opEmptySuccess, - opFixedSuccess, - opKnownAlternativeFailure, - opKnownHttpFailure, - opSuccessFromHttp, - opUnknownFailure, - opUnknownHttpFailure, -} from "../operation.js"; -import { EddsaPrivP, encodeCrock } from "../taler-crypto.js"; -import { - AccessToken, - AmountString, - OfficerAccount, - PaginationParams, - ReserveAccount, - codecForTalerCommonConfigResponse, -} from "../types-taler-common.js"; -import { - AccountKycStatus, - AmlDecisionRequest, - BatchWithdrawResponse, - ExchangeGetContractResponse, - ExchangeKycUploadFormRequest, - ExchangeLegacyBatchWithdrawRequest, - ExchangeMergeConflictResponse, - ExchangeMergeSuccessResponse, - ExchangePurseDeposits, - ExchangePurseMergeRequest, - ExchangePurseStatus, - ExchangeReservePurseRequest, - ExchangeVersionResponse, - KycRequirementInformationId, - LegitimizationNeededResponse, - PurseConflict, - PurseConflictPartial, - WalletKycRequest, - codecForAccountKycStatus, - codecForAmlDecisionsResponse, - codecForAmlKycAttributes, - codecForAmlWalletKycCheckResponse, - codecForAvailableMeasureSummary, - codecForEventCounter, - codecForExchangeConfig, - codecForExchangeGetContractResponse, - codecForExchangeKeysResponse, - codecForExchangeMergeConflictResponse, - codecForExchangeMergeSuccessResponse, - codecForExchangePurseStatus, - codecForExchangeTransferList, - codecForKycProcessClientInformation, - codecForKycProcessStartInformation, - codecForLegitimizationNeededResponse, - codecForPurseConflict, - codecForPurseConflictPartial, -} from "../types-taler-exchange.js"; -import { CacheEvictor, addPaginationParams, nullEvictor } from "./utils.js"; - -import { TalerError } from "../errors.js"; -import { - AmountJson, - Amounts, - CancellationToken, - LongpollQueue, - signAmlDecision, - signAmlQuery, - signKycAuth, - signWalletAccountSetup, -} from "../index.js"; -import { TalerErrorCode } from "../taler-error-codes.js"; -import { AbsoluteTime } from "../time.js"; -import { codecForEmptyObject } from "../types-taler-wallet.js"; - -export type TalerExchangeResultByMethod< - prop extends keyof TalerExchangeHttpClient, -> = ResultByMethod<TalerExchangeHttpClient, prop>; -export type TalerExchangeErrorsByMethod< - prop extends keyof TalerExchangeHttpClient, -> = FailCasesByMethod<TalerExchangeHttpClient, prop>; - -export enum TalerExchangeCacheEviction { - UPLOAD_KYC_FORM, - MAKE_AML_DECISION, -} - -declare const __pubId: unique symbol; -export type ReservePub = string & { [__pubId]: true }; -/** - */ -export class TalerExchangeHttpClient { - public readonly PROTOCOL_VERSION = "21:0:0"; - private httpLib: HttpRequestLibrary; - private cacheEvictor: CacheEvictor<TalerExchangeCacheEviction>; - private preventCompression: boolean; - private cancelationToken: CancellationToken; - private longPollQueue: LongpollQueue; - - constructor( - readonly baseUrl: string, - params: { - httpClient?: HttpRequestLibrary; - cacheEvictor?: CacheEvictor<TalerExchangeCacheEviction>; - preventCompression?: boolean; - cancelationToken?: CancellationToken; - longPollQueue?: LongpollQueue; - }, - ) { - this.httpLib = params.httpClient ?? createPlatformHttpLib(); - this.cacheEvictor = params.cacheEvictor ?? nullEvictor; - this.preventCompression = !!params.preventCompression; - this.cancelationToken = - params.cancelationToken ?? CancellationToken.CONTINUE; - this.longPollQueue = params.longPollQueue ?? new LongpollQueue(); - } - - isCompatible(version: string): boolean { - const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); - return compare?.compatible ?? false; - } - - private async fetch( - url_or_path: URL | string, - opts: HttpRequestOptions = {}, - longpoll: boolean = false, - ): Promise<HttpResponse> { - const url = - typeof url_or_path == "string" - ? new URL(url_or_path, this.baseUrl) - : url_or_path; - if (longpoll || url.searchParams.has("timeout_ms")) { - return this.longPollQueue.run( - url, - this.cancelationToken, - async (timeoutMs) => { - url.searchParams.set("timeout_ms", String(timeoutMs)); - return this.httpLib.fetch(url.href, { - cancellationToken: this.cancelationToken, - ...opts, - }); - }, - ); - } else { - return this.httpLib.fetch(url.href, { - cancellationToken: this.cancelationToken, - ...opts, - }); - } - } - - // TERMS - - /** - * https://docs.taler.net/core/api-exchange.html#get--seed - * - */ - /** - * https://docs.taler.net/core/api-exchange.html#get--seed - * - */ - - // EXCHANGE INFORMATION - - /** - * https://docs.taler.net/core/api-exchange.html#get--seed - * - */ - async getSeed(): Promise< - | OperationOk<Uint8Array<ArrayBuffer>> - | OperationFail<HttpStatusCode.NotFound> - > { - const resp = await this.fetch("seed"); - switch (resp.status) { - case HttpStatusCode.Ok: - const buffer = await resp.bytes(); - const uintar = new Uint8Array(buffer); - return opFixedSuccess(uintar); - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - /** - * https://docs.taler.net/core/api-exchange.html#get--config - * - */ - async getConfig(): Promise< - | OperationFail<HttpStatusCode.NotFound> - | OperationOk<ExchangeVersionResponse> - > { - const resp = await this.fetch("config"); - switch (resp.status) { - case HttpStatusCode.Ok: { - const minBody = await readSuccessResponseJsonOrThrow( - resp, - codecForTalerCommonConfigResponse(), - ); - const expectedName = "taler-exchange"; - if (minBody.name !== expectedName) { - throw TalerError.fromUncheckedDetail({ - code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, - requestUrl: resp.requestUrl, - httpStatusCode: resp.status, - detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`, - }); - } - if (!this.isCompatible(minBody.version)) { - throw TalerError.fromUncheckedDetail({ - code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION, - requestUrl: resp.requestUrl, - httpStatusCode: resp.status, - detail: `Unsupported protocol version, client supports ${this.PROTOCOL_VERSION}, server supports ${minBody.version}`, - }); - } - // Now that we've checked the basic body, re-parse the full response. - const body = await readSuccessResponseJsonOrThrow( - resp, - codecForExchangeConfig(), - ); - return { - type: "ok", - case: "ok", - body, - }; - } - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-merchant.html#get--config - * - * PARTIALLY IMPLEMENTED!! - */ - async getKeys() { - const resp = await this.fetch("keys"); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForExchangeKeysResponse()); - default: - return opUnknownHttpFailure(resp); - } - } - - // - // MANAGEMENT - // - - /** - * https://docs.taler.net/core/api-exchange.html#get--management-keys - * - */ - async getFutureKeys(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-keys - * - */ - async signFutureKeys(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-denominations-$H_DENOM_PUB-revoke - * - */ - async revokeFutureDenominationKeys(): Promise<never> { - throw Error("not yet implemented"); - } - /** - * https://docs.taler.net/core/api-exchange.html#post--management-signkeys-$EXCHANGE_PUB-revoke - * - */ - async revokeFutureSigningKeys(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-auditors - * - */ - async enableAuditor(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-auditors-$AUDITOR_PUB-disable - * - */ - async disableAuditor(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-wire-fee - * - */ - async configWireFee(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-global-fees - * - */ - async configGlobalFees(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-wire - * - */ - async enableWireMethod(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-wire-disable - * - */ - async disableWireMethod(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-drain - * - */ - async drainProfits(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-aml-officers - * - */ - async updateOfficer(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--management-partners - * - */ - async enablePartner(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // AUDITOR - // - - /** - * https://docs.taler.net/core/api-exchange.html#post--auditors-$AUDITOR_PUB-$H_DENOM_PUB - * - */ - async addAuditor(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // WITHDRAWAL - // - - /** - * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB - * - */ - async getReserveInfo(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--csr-withdraw - * - */ - async prepareCsrWithdawal(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-batch-withdraw - * - */ - async withdraw(rid: ReservePub, body: ExchangeLegacyBatchWithdrawRequest) { - const resp = await this.fetch(`reserves/${rid}/batch-withdraw`, { - method: "POST", - body, - }); - - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp( - resp, - codecForAny() as Codec<BatchWithdrawResponse>, - ); - case HttpStatusCode.Forbidden: - case HttpStatusCode.BadRequest: - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - case HttpStatusCode.Gone: - return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.UnavailableForLegalReasons: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForLegitimizationNeededResponse(), - ); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction - * - */ - async withdrawWithAge(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--age-withdraw-$ACH-reveal - * - */ - async revealCoinsForAge(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // RESERVE HISTORY - // - - /** - * https://docs.taler.net/core/api-exchange.html#get--reserves-$RESERVE_PUB-history - * - */ - async getResverveHistory(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // COIN HISTORY - // - - /** - * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-history - * - */ - async getCoinHistory(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // DEPOSIT - // - - /** - * https://docs.taler.net/core/api-exchange.html#post--batch-deposit - * - */ - async deposit(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // REFRESH - // - - /** - * https://docs.taler.net/core/api-exchange.html#post--csr-melt - * - */ - async prepareCsrMelt(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-melt - * - */ - async meltCoin(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-link - * - */ - async linkCoin(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // RECOUP - // - - /** - * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup - * - */ - async recoupReserveCoin(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-recoup-refresh - * - */ - async recoupRefreshCoin(): Promise<never> { - throw Error("not yet implemented"); - } - - // WIRE TRANSFER - - /** - * https://docs.taler.net/core/api-exchange.html#get--transfers-$WTID - * - */ - async getWireTransferInfo(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--deposits-$H_WIRE-$MERCHANT_PUB-$H_CONTRACT_TERMS-$COIN_PUB - * - */ - async getWireTransferIdForDeposit(): Promise<never> { - throw Error("not yet implemented"); - } - - // REFUND - - /** - * https://docs.taler.net/core/api-exchange.html#post--coins-$COIN_PUB-refund - * - */ - async refund(): Promise<never> { - throw Error("not yet implemented"); - } - - // WALLET TO WALLET - - /** - * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-merge - * - */ - async getPurseStatusAtMerge( - pursePub: string, - longpoll: boolean = false, - ): Promise< - | OperationOk<ExchangePurseStatus> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Gone> - > { - const resp = await this.fetch(`purses/${pursePub}/merge`, {}, longpoll); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForExchangePurseStatus()); - case HttpStatusCode.Gone: - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--purses-$PURSE_PUB-deposit - * - */ - async getPurseStatusAtDeposit( - pursePub: string, - longpoll: boolean = false, - ): Promise< - | OperationOk<ExchangePurseStatus> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Gone> - > { - const resp = await this.fetch(`purses/${pursePub}/deposit`, {}, longpoll); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForExchangePurseStatus()); - case HttpStatusCode.Gone: - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-create - * - */ - async createPurseFromDeposit( - pursePub: string, - body: any, // FIXME - ): Promise< - | OperationOk<void> - | OperationFail<HttpStatusCode.Forbidden> - | OperationFail<HttpStatusCode.NotFound> - | OperationAlternative<HttpStatusCode.Conflict, PurseConflict> - | OperationFail<HttpStatusCode.TooEarly> - > { - const resp = await this.fetch(`purses/${pursePub}/create`, { - method: "POST", - body, - }); - switch (resp.status) { - case HttpStatusCode.Ok: - // FIXME: parse PurseCreateSuccessResponse - return opSuccessFromHttp(resp, codecForAny()); - case HttpStatusCode.Conflict: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForPurseConflict(), - ); - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.TooEarly: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#delete--purses-$PURSE_PUB - * - */ - async deletePurse( - pursePub: string, - purseSig: string, - ): Promise< - | OperationOk<void> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Conflict> - | OperationFail<HttpStatusCode.Forbidden> - > { - const resp = await this.fetch(`purses/${pursePub}`, { - method: "DELETE", - headers: { - "taler-purse-signature": purseSig, - }, - }); - switch (resp.status) { - case HttpStatusCode.NoContent: - return opEmptySuccess(resp); - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.Conflict: - return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.Forbidden: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * POST /purses/$PURSE_PUB/merge - * - * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge - */ - async postPurseMerge( - pursePub: string, - body: ExchangePurseMergeRequest, - ): Promise< - | OperationOk<ExchangeMergeSuccessResponse> - | OperationAlternative< - HttpStatusCode.UnavailableForLegalReasons, - LegitimizationNeededResponse - > - | OperationAlternative< - HttpStatusCode.Conflict, - ExchangeMergeConflictResponse - > - | OperationFail<HttpStatusCode.Forbidden> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Gone> - > { - const resp = await this.fetch(`purses/${pursePub}/merge`, { - method: "POST", - body, - }); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForExchangeMergeSuccessResponse()); - case HttpStatusCode.UnavailableForLegalReasons: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForLegitimizationNeededResponse(), - ); - case HttpStatusCode.Conflict: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForExchangeMergeConflictResponse(), - ); - case HttpStatusCode.Gone: - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-purse - * - */ - async createPurseFromReserve( - pursePub: string, - body: ExchangeReservePurseRequest, - ): Promise< - | OperationOk<void> - | OperationFail<HttpStatusCode.PaymentRequired> - | OperationFail<HttpStatusCode.Forbidden> - | OperationFail<HttpStatusCode.NotFound> - | OperationAlternative<HttpStatusCode.Conflict, PurseConflictPartial> - | OperationAlternative< - HttpStatusCode.UnavailableForLegalReasons, - LegitimizationNeededResponse - > - > { - const resp = await this.fetch(`reserves/${pursePub}/purse`, { - method: "POST", - body, - }); - switch (resp.status) { - case HttpStatusCode.Ok: - // FIXME: parse PurseCreateSuccessResponse - return opSuccessFromHttp(resp, codecForAny()); - case HttpStatusCode.PaymentRequired: - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.Conflict: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForPurseConflictPartial(), - ); - case HttpStatusCode.UnavailableForLegalReasons: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForLegitimizationNeededResponse(), - ); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--contracts-$CONTRACT_PUB - * - */ - async getContract( - pursePub: string, - ): Promise< - | OperationOk<ExchangeGetContractResponse> - | OperationFail<HttpStatusCode.NotFound> - > { - const resp = await this.fetch(`contracts/${pursePub}`); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForExchangeGetContractResponse()); - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-deposit - * - */ - async depositIntoPurse( - pursePub: string, - body: ExchangePurseDeposits, - ): Promise< - | OperationOk<void> - | OperationAlternative<HttpStatusCode.Conflict, PurseConflict> - | OperationFail<HttpStatusCode.Forbidden> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Gone> - > { - const resp = await this.fetch(`purses/${pursePub}/deposit`, { - method: "POST", - body, - }); - switch (resp.status) { - case HttpStatusCode.Ok: - // FIXME: parse PurseDepositSuccessResponse - return opSuccessFromHttp(resp, codecForAny()); - case HttpStatusCode.Conflict: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForPurseConflict(), - ); - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.Gone: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - // WADS - - /** - * https://docs.taler.net/core/api-exchange.html#get--wads-$WAD_ID - * - */ - async getWadInfo(): Promise<never> { - throw Error("not yet implemented"); - } - - // - // KYC - // - - /** - * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet - * - */ - async notifyKycBalanceLimit(account: ReserveAccount, balance: AmountString) { - const body: WalletKycRequest = { - balance, - reserve_pub: account.id, - reserve_sig: encodeCrock( - signWalletAccountSetup(account.signingKey, balance), - ), - }; - const resp = await this.fetch(`kyc-wallet`, { - method: "POST", - body, - }); - - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForAmlWalletKycCheckResponse()); - case HttpStatusCode.NoContent: - return opEmptySuccess(resp); - case HttpStatusCode.Forbidden: - return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.UnavailableForLegalReasons: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForLegitimizationNeededResponse(), - ); - default: - return opUnknownFailure(resp, await readTalerErrorResponse(resp)); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--kyc-check-$H_NORMALIZED_PAYTO - * - */ - async checkKycStatus( - signingKey: EddsaPrivP | string, - paytoHash: string, - longpoll: boolean = false, - params: { - awaitAuth?: boolean; - } = {}, - ): Promise< - | OperationOk<void> - | OperationAlternative<HttpStatusCode.Ok, AccountKycStatus> - | OperationAlternative<HttpStatusCode.Accepted, AccountKycStatus> - | OperationFail<HttpStatusCode.Forbidden> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Conflict> - > { - const url = new URL(`kyc-check/${paytoHash}`, this.baseUrl); - if (params.awaitAuth !== undefined) { - url.searchParams.set("await_auth", params.awaitAuth ? "YES" : "NO"); - } - - const signature = - typeof signingKey === "string" - ? signingKey - : encodeCrock(signKycAuth(signingKey)); - - const resp = await this.fetch( - url, - { - headers: { - "Account-Owner-Signature": signature, - }, - }, - longpoll, - ); - - switch (resp.status) { - case HttpStatusCode.Ok: - case HttpStatusCode.Accepted: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForAccountKycStatus(), - ); - case HttpStatusCode.NoContent: - return opEmptySuccess(resp); - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--kyc-info-$ACCESS_TOKEN - * - */ - async checkKycInfo( - token: AccessToken, - known: KycRequirementInformationId[], - longpoll: boolean = false, - ) { - const resp = await this.fetch( - `kyc-info/${token}`, - { - method: "GET", - headers: { - "If-None-Match": known.length ? known.join(",") : undefined, - }, - }, - longpoll, - ); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForKycProcessClientInformation()); - case HttpStatusCode.Accepted: - case HttpStatusCode.NoContent: - return opKnownAlternativeFailure( - resp, - resp.status, - codecForEmptyObject(), - ); - case HttpStatusCode.NotModified: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownFailure(resp, await readTalerErrorResponse(resp)); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--kyc-upload-$ID - * - */ - async uploadKycForm<T extends ExchangeKycUploadFormRequest>( - requirement: KycRequirementInformationId, - body: T, - ) { - const resp = await this.fetch(`kyc-upload/${requirement}`, { - method: "POST", - body, - compress: this.preventCompression ? undefined : "deflate", - }); - switch (resp.status) { - case HttpStatusCode.NoContent: { - this.cacheEvictor.notifySuccess( - TalerExchangeCacheEviction.UPLOAD_KYC_FORM, - ); - return opEmptySuccess(resp); - } - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - case HttpStatusCode.PayloadTooLarge: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--kyc-start-$ID - * - */ - async startExternalKycProcess( - requirement: KycRequirementInformationId, - body: object = {}, - ) { - const resp = await this.fetch(`kyc-start/${requirement}`, { - method: "POST", - body, - }); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForKycProcessStartInformation()); - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - case HttpStatusCode.PayloadTooLarge: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--kyc-proof-$PROVIDER_NAME?state=$H_PAYTO - * - */ - async completeExternalKycProcess( - provider: string, - state: string, - code: string, - ) { - const resp = await this.fetch( - `kyc-proof/${provider}?state=${state}&code=${code}`, - { - method: "GET", - redirect: "manual", - }, - ); - - switch (resp.status) { - case HttpStatusCode.SeeOther: - return opEmptySuccess(resp); - case HttpStatusCode.NotFound: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - // - // AML operations - // - - /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures - * - */ - async getAmlMesasures(auth: OfficerAccount) { - const resp = await this.fetch(`aml/${auth.id}/measures`, { - method: "GET", - headers: { - "Taler-AML-Officer-Signature": encodeCrock( - signAmlQuery(auth.signingKey), - ), - }, - }); - - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForAvailableMeasureSummary()); - case HttpStatusCode.Conflict: - case HttpStatusCode.NotFound: - case HttpStatusCode.Forbidden: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures - * - */ - async getAmlKycStatistics( - auth: OfficerAccount, - name: string, - filter: { - since?: AbsoluteTime; - until?: AbsoluteTime; - } = {}, - ) { - const url = new URL(`aml/${auth.id}/kyc-statistics/${name}`, this.baseUrl); - - if (filter.since !== undefined && filter.since.t_ms !== "never") { - url.searchParams.set("start_date", String(filter.since.t_ms)); - } - if (filter.until !== undefined && filter.until.t_ms !== "never") { - url.searchParams.set("end_date", String(filter.until.t_ms)); - } - - const resp = await this.fetch(url, { - method: "GET", - headers: { - "Taler-AML-Officer-Signature": encodeCrock( - signAmlQuery(auth.signingKey), - ), - }, - }); - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForEventCounter()); - case HttpStatusCode.Conflict: - case HttpStatusCode.NotFound: - case HttpStatusCode.Forbidden: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions - * - */ - async getAmlDecisions( - auth: OfficerAccount, - params: PaginationParams & { - account?: string; - active?: boolean; - investigation?: boolean; - } = {}, - ) { - const url = new URL(`aml/${auth.id}/decisions`, this.baseUrl); - - addPaginationParams(url, params); - if (params.account !== undefined) { - url.searchParams.set("h_payto", params.account); - } - if (params.active !== undefined) { - url.searchParams.set("active", params.active ? "YES" : "NO"); - } - if (params.investigation !== undefined) { - url.searchParams.set( - "investigation", - params.investigation ? "YES" : "NO", - ); - } - - const resp = await this.fetch(url, { - headers: { - "Taler-AML-Officer-Signature": encodeCrock( - signAmlQuery(auth.signingKey), - ), - }, - }); - - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForAmlDecisionsResponse()); - case HttpStatusCode.NoContent: - return opFixedSuccess({ records: [] }); - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO - * - */ - async getAmlAttributesForAccount( - auth: OfficerAccount, - account: string, - params: PaginationParams = {}, - ) { - const url = new URL(`aml/${auth.id}/attributes/${account}`, this.baseUrl); - - addPaginationParams(url, params); - const resp = await this.fetch(url, { - headers: { - "Taler-AML-Officer-Signature": encodeCrock( - signAmlQuery(auth.signingKey), - ), - }, - }); - - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForAmlKycAttributes()); - case HttpStatusCode.NoContent: - return opFixedSuccess({ details: [] }); - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision - * - */ - async makeAmlDesicion( - auth: OfficerAccount, - decision: Omit<AmlDecisionRequest, "officer_sig">, - ) { - const body: AmlDecisionRequest = { - officer_sig: encodeCrock(signAmlDecision(auth.signingKey, decision)), - ...decision, - }; - const resp = await this.fetch(`aml/${auth.id}/decision`, { - method: "POST", - headers: { - "Taler-AML-Officer-Signature": encodeCrock( - signAmlQuery(auth.signingKey), - ), - }, - body, - compress: this.preventCompression ? undefined : "deflate", - }); - - switch (resp.status) { - case HttpStatusCode.NoContent: { - this.cacheEvictor.notifySuccess( - TalerExchangeCacheEviction.MAKE_AML_DECISION, - ); - return opEmptySuccess(resp); - } - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-transfers-credit - * - */ - async getTransfersCredit( - auth: OfficerAccount, - params: PaginationParams & { threshold?: AmountJson } = {}, - ) { - const url = new URL(`aml/${auth.id}/transfers-credit`, this.baseUrl); - - addPaginationParams(url, params); - - if (params.threshold) { - url.searchParams.set("threshold", Amounts.stringify(params.threshold)); - } - - const resp = await this.fetch(url, { - headers: { - "Taler-AML-Officer-Signature": encodeCrock( - signAmlQuery(auth.signingKey), - ), - }, - }); - - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForExchangeTransferList()); - case HttpStatusCode.NoContent: - return opFixedSuccess({ transfers: [] }); - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-transfers-debit - * - */ - async getTransfersDebit( - auth: OfficerAccount, - params: PaginationParams & { threshold?: AmountJson } = {}, - ) { - const url = new URL(`aml/${auth.id}/transfers-debit`, this.baseUrl); - - addPaginationParams(url, params); - - if (params.threshold) { - url.searchParams.set("threshold", Amounts.stringify(params.threshold)); - } - - const resp = await this.fetch(url, { - headers: { - "Taler-AML-Officer-Signature": encodeCrock( - signAmlQuery(auth.signingKey), - ), - }, - }); - - switch (resp.status) { - case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForExchangeTransferList()); - case HttpStatusCode.NoContent: - return opFixedSuccess({ transfers: [] }); - case HttpStatusCode.Forbidden: - case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - // RESERVE control - - /** - * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-open - * - */ - async reserveOpen(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#get--reserves-attest-$RESERVE_PUB - * - */ - async getReserveAttributes(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--reserves-attest-$RESERVE_PUB - * - */ - async signReserveAttributes(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#post--reserves-$RESERVE_PUB-close - * - */ - async closeReserve(): Promise<never> { - throw Error("not yet implemented"); - } - - /** - * https://docs.taler.net/core/api-exchange.html#delete--reserves-$RESERVE_PUB - * - */ - async deleteReserve(): Promise<never> { - throw Error("not yet implemented"); - } -} diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts @@ -16,7 +16,7 @@ export * from "./http-client/bank-integration.js"; export * from "./http-client/bank-revenue.js"; export * from "./http-client/bank-wire.js"; export * from "./http-client/challenger.js"; -export * from "./http-client/exchange.js"; +export * from "./http-client/exchange-client.js"; export * from "./http-client/merchant.js"; export * from "./http-client/officer-account.js"; @@ -33,6 +33,7 @@ export * from "./invariants.js"; export * from "./kdf.js"; export * from "./libtool-version.js"; export * from "./logging.js"; +export * from "./longpool-queue.js"; export { crypto_sign_keyPair_fromSeed, randomBytes, @@ -57,7 +58,6 @@ export * from "./time.js"; export * from "./timer.js"; export * from "./transaction-test-data.js"; export * from "./url.js"; -export * from "./longpool-queue.js" // FIXME: remove all this, needs refactor export * from "./types-taler-bank-conversion.js"; @@ -87,8 +87,8 @@ export * from "./account-restrictions.js"; export * from "./aml/aml-events.js"; export * from "./aml/aml-properties.js"; -export * from "./taler-form-attributes.js"; export * from "./taler-account-properties.js"; +export * from "./taler-form-attributes.js"; export * from "./iso-3166.js"; export * from "./iso-4217.js"; diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts @@ -14,12 +14,15 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { TalerExchangeHttpClient } from "@gnu-taler/taler-util"; + /** * Protocol version spoken with the exchange. * * Uses libtool's current:revision:age versioning. */ -export const WALLET_EXCHANGE_PROTOCOL_VERSION = "17:0:0"; +export const WALLET_EXCHANGE_PROTOCOL_VERSION = + TalerExchangeHttpClient.SUPPORTED_EXCHANGE_PROTOCOL_VERSION; /** * Protocol version spoken with the merchant. diff --git a/packages/web-util/src/context/exchange-api.ts b/packages/web-util/src/context/exchange-api.ts @@ -56,8 +56,8 @@ export type ExchangeContextType = { /** * Do not use. This is here because the AML dashboard does too many * request to the event API - * - * FIXME: The server should expose a better API + * + * FIXME: The server should expose a better API * https://bugs.gnunet.org/view.php?id=9776 * @deprecated */ @@ -171,7 +171,12 @@ export const ExchangeApiProvider = ({ }); } - const { lib: unthrottledApi } = buildExchangeApiClient(baseUrl, evictors, !!preventCompression, true) + const { lib: unthrottledApi } = buildExchangeApiClient( + baseUrl, + evictors, + !!preventCompression, + true, + ); const value: ExchangeContextType = { url: baseUrl, @@ -206,7 +211,11 @@ function buildExchangeApiClient( }, }); - const ex = new TalerExchangeHttpClient(url.href, { httpClient: httpLib, cacheEvictor: evictors.exchange, preventCompression }); + const ex = new TalerExchangeHttpClient(url.href, { + httpClient: httpLib, + cacheEvictor: evictors.exchange, + preventCompression, + }); async function getRemoteConfig(): Promise<KeysAndConfigType> { const configResp = await ex.getConfig(); @@ -237,7 +246,7 @@ function buildExchangeApiClient( return { getRemoteConfig, - VERSION: ex.PROTOCOL_VERSION, + VERSION: TalerExchangeHttpClient.SUPPORTED_EXCHANGE_PROTOCOL_VERSION, lib: { exchange: ex, },