diff --git a/packages/taler-util/src/http-client/core-bank.ts b/packages/taler-util/src/http-client/core-bank.ts
new file mode 100644
index 000000000..765348e42
--- /dev/null
+++ b/packages/taler-util/src/http-client/core-bank.ts
@@ -0,0 +1,523 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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
+ */
+
+import {
+ AmountJson,
+ Amounts,
+ Logger
+} from "@gnu-taler/taler-util";
+import {
+ createPlatformHttpLib,
+ expectSuccessResponseOrThrow,
+ HttpRequestLibrary,
+ readSuccessResponseJsonOrThrow
+} from "@gnu-taler/taler-util/http";
+import { AccessToken, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashouts, codecForCashoutStatusResponse, codecForConversionRatesResponse, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse, codecForTokenSuccessResponse, TalerAuthentication, TalerCorebankApi } from "./types.js";
+import { addPaginationParams, makeBasicAuthHeader, makeBearerTokenAuthHeader, PaginationParams, UserAndPassword, UserAndToken } from "./utils.js";
+
+const logger = new Logger("http-client/core-bank.ts");
+
+export class TalerCoreBankHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token
+ *
+ * @returns
+ */
+ async createAccessToken(
+ auth: UserAndPassword,
+ body: TalerAuthentication.TokenRequest,
+ ): Promise {
+ const url = new URL(`accounts/${auth.username}/token`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBasicAuthHeader(auth.username, auth.password),
+ },
+ body
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForTokenSuccessResponse());
+ }
+
+ async deleteAccessToken(
+ auth: UserAndToken,
+ ): Promise {
+ const url = new URL(`accounts/${auth.username}/token`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "DELETE",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token),
+ }
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
+ *
+ */
+ async getConfig(): Promise {
+ const url = new URL(`config`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET"
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCoreBankConfig());
+ }
+
+ //
+ // ACCOUNTS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts
+ *
+ */
+ async createAccount(auth: AccessToken, body: TalerCorebankApi.RegisterAccountRequest): Promise {
+ const url = new URL(`accounts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body,
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#delete--accounts-$USERNAME
+ *
+ */
+ async deleteAccount(auth: UserAndToken): Promise {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "DELETE",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME
+ *
+ */
+ async updateAccount(auth: UserAndToken, body: TalerCorebankApi.AccountReconfiguration): Promise {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "PATCH",
+ body,
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME-auth
+ *
+ */
+ async updatePassword(auth: UserAndToken, body: TalerCorebankApi.AccountPasswordChange): Promise {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "PATCH",
+ body,
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/get-$BANK_API_BASE_URL-public-accounts
+ *
+ */
+ async getPublicAccounts(): Promise {
+ const url = new URL(`public-accounts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForPublicAccountsResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts
+ *
+ */
+ async getAccounts(auth: AccessToken): Promise {
+ const url = new URL(`accounts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForListBankAccountsResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
+ *
+ */
+ async getAccount(auth: UserAndToken): Promise {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForAccountData());
+ }
+
+ //
+ // TRANSACTIONS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions
+ *
+ */
+ async getTransactions(auth: UserAndToken, pagination?: PaginationParams): Promise {
+ const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
+ addPaginationParams(url, pagination)
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionsResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions-$transaction_id
+ *
+ */
+ async getTransactionById(auth: UserAndToken, txid: number): Promise {
+ const url = new URL(`accounts/${auth.username}/transactions/${String(txid)}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionInfo());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-transactions
+ *
+ */
+ async createTransaction(auth: UserAndToken, body: TalerCorebankApi.CreateBankAccountTransactionCreate): Promise {
+ const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ //
+ // WITHDRAWALS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
+ *
+ */
+ async createWithdrawal(auth: UserAndToken, body: TalerCorebankApi.BankAccountCreateWithdrawalRequest): Promise {
+ const url = new URL(`accounts/${auth.username}/withdrawals`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountCreateWithdrawalResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
+ *
+ */
+ async getWithdrawalById(wid: string): Promise {
+ const url = new URL(`withdrawals/${wid}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountGetWithdrawalResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort
+ *
+ */
+ async abortWithdrawalById(wid: string): Promise {
+ const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm
+ *
+ */
+ async confirmWithdrawalById(wid: string): Promise {
+ const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ //
+ // CASHOUTS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts
+ *
+ */
+ async createCashout(auth: UserAndToken, body: TalerCorebankApi.CashoutRequest): Promise {
+ const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashoutPending());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-abort
+ *
+ */
+ async abortCashoutById(auth: UserAndToken, cid: string): Promise {
+ const url = new URL(`accounts/${auth.username}/cashouts/${cid}/abort`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
+ *
+ */
+ async confirmCashoutById(auth: UserAndToken, cid: string, body: TalerCorebankApi.CashoutConfirmRequest): Promise {
+ const url = new URL(`accounts/${auth.username}/cashouts/${cid}/confirm`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
+ *
+ */
+ async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson }): Promise {
+ const url = new URL(`cashout-rate`, this.baseUrl);
+ if (conversion.debit) {
+ url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit))
+ }
+ if (conversion.credit) {
+ url.searchParams.set("amount_debit", Amounts.stringify(conversion.credit))
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashoutConversionResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
+ *
+ */
+ async getAccountCashouts(auth: UserAndToken): Promise {
+ const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashouts());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--cashouts
+ *
+ */
+ async getGlobalCashouts(auth: AccessToken): Promise {
+ const url = new URL(`cashouts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForGlobalCashouts());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
+ *
+ */
+ async getCashoutById(auth: UserAndToken, cid: string): Promise {
+ const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashoutStatusResponse());
+ }
+
+ //
+ // CONVERSION RATE
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--conversion-rates
+ *
+ */
+ async getConversionRates(): Promise {
+ const url = new URL(`conversion-rates`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForConversionRatesResponse());
+ }
+
+ //
+ // MONITOR
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--monitor
+ *
+ */
+ async getMonitor(params: { timeframe: TalerCorebankApi.MonitorTimeframeParam, which: number }): Promise {
+ const url = new URL(`monitor`, this.baseUrl);
+ url.searchParams.set("timeframe", params.timeframe.toString())
+ url.searchParams.set("which", String(params.which))
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForMonitorResponse());
+ }
+
+ //
+ // Others API
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
+ *
+ */
+ getIntegrationAPI(): TalerBankIntegrationHttpClient {
+ const url = new URL(`taler-integration`, this.baseUrl);
+ return new TalerBankIntegrationHttpClient(url.href, this.httpLib)
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
+ *
+ */
+ getWireGatewayAPI(username: string): TalerWireGatewayHttpClient {
+ const url = new URL(`accounts/${username}/taler-wire-gateway`, this.baseUrl);
+ return new TalerWireGatewayHttpClient(url.href, username, this.httpLib)
+ }
+
+ /**
+* https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
+*
+*/
+ getRevenueAPI(username: string): TalerRevenueHttpClient {
+ const url = new URL(`accounts/${username}/taler-revenue`, this.baseUrl);
+ return new TalerRevenueHttpClient(url.href, username, this.httpLib,)
+ }
+
+}
+
+export class TalerBankIntegrationHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+}
+
+export class TalerWireGatewayHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ private username: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+}
+
+export class TalerRevenueHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ private username: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+}
\ No newline at end of file
diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts
new file mode 100644
index 000000000..047f2fe8e
--- /dev/null
+++ b/packages/taler-util/src/http-client/types.ts
@@ -0,0 +1,703 @@
+import { codecForAmountJson, codecForAmountString } from "../amounts.js";
+import { TalerCorebankApiClient } from "../bank-api-client.js";
+import { Codec, buildCodecForObject, codecForAny, codecForBoolean, codecForConstString, codecForEither, codecForList, codecForMap, codecForNumber, codecForString, codecOptional } from "../codec.js";
+import { codecForTimestamp } from "../time.js";
+
+
+type HashCode = string;
+type EddsaPublicKey = string;
+type EddsaSignature = string;
+type WireTransferIdentifierRawP = string;
+type RelativeTime = {
+ d_us: number | "forever"
+};
+type ImageDataUrl = string;
+
+interface WithId {
+ id: string;
+}
+
+interface Timestamp {
+ // Milliseconds since epoch, or the special
+ // value "forever" to represent an event that will
+ // never happen.
+ t_s: number | "never";
+}
+interface Duration {
+ d_us: number | "forever";
+}
+
+interface WithId {
+ id: string;
+}
+
+type UUID = string;
+type Integer = number;
+
+type Amount = string;
+
+
+export interface LoginToken {
+ token: AccessToken,
+ expiration: Timestamp,
+}
+// token used to get loginToken
+// must forget after used
+declare const __ac_token: unique symbol;
+export type AccessToken = string & {
+ [__ac_token]: true;
+};
+
+export namespace TalerAuthentication {
+
+ export interface TokenRequest {
+ // Service-defined scope for the token.
+ // Typical scopes would be "readonly" or "readwrite".
+ scope: string;
+
+ // Server may impose its own upper bound
+ // on the token validity duration
+ duration?: RelativeTime;
+
+ // Is the token refreshable into a new token during its
+ // validity?
+ // Refreshable tokens effectively provide indefinite
+ // access if they are refreshed in time.
+ refreshable?: boolean;
+ }
+
+ export interface TokenSuccessResponse {
+ // Expiration determined by the server.
+ // Can be based on the token_duration
+ // from the request, but ultimately the
+ // server decides the expiration.
+ expiration: Timestamp;
+
+ // Opque access token.
+ access_token: AccessToken;
+ }
+}
+
+interface CurrencySpecification {
+
+ // Name of the currency.
+ name: string;
+
+ // Decimal separator for fractional digits.
+ decimal_separator: string;
+
+ // how many digits the user may enter after the decimal_separator
+ num_fractional_input_digits: Integer;
+
+ // Number of fractional digits to render in normal font and size.
+ num_fractional_normal_digits: Integer;
+
+ // Number of fractional digits to render always, if needed by
+ // padding with zeros.
+ num_fractional_trailing_zero_digits: Integer;
+
+ // Whether the currency name should be rendered before (true) or
+ // after (false) the numeric value
+ is_currency_name_leading: boolean;
+
+ // map of powers of 10 to alternative currency names / symbols, must
+ // always have an entry under "0" that defines the base name,
+ // e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC".
+ // Communicates the currency symbol to be used.
+ alt_unit_names: { [log10: string]: string };
+}
+
+export const codecForAccessToken = codecForString as () => Codec;
+export const codecForTokenSuccessResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("access_token", codecForAccessToken())
+ .property("expiration", codecForTimestamp)
+ .build("TalerAuthentication.TokenSuccessResponse")
+
+export const codecForCurrencySpecificiation =
+ (): Codec =>
+ buildCodecForObject()
+ .property("name", codecForString())
+ .property("decimal_separator", codecForString())
+ .property("num_fractional_input_digits", codecForNumber())
+ .property("num_fractional_normal_digits", codecForNumber())
+ .property("num_fractional_trailing_zero_digits", codecForNumber())
+ .property("is_currency_name_leading", codecForBoolean())
+ .property("alt_unit_names", codecForMap(codecForString()))
+ .build("CurrencySpecification")
+
+export const codecForCoreBankConfig =
+ (): Codec =>
+ buildCodecForObject()
+ .property("name", codecForString())
+ .property("version", codecForString())
+ .property("have_cashout", codecOptional(codecForBoolean()))
+ .property("currency", codecForCurrencySpecificiation())
+ .property("fiat_currency", codecOptional(codecForCurrencySpecificiation()))
+ .build("TalerCorebankApi.Config")
+
+const codecForBalance = (): Codec =>
+ buildCodecForObject()
+ .property("amount", codecForAmountString())
+ .property("credit_debit_indicator", codecForEither(codecForConstString("credit"), codecForConstString("debit")))
+ .build("TalerCorebankApi.Balance")
+
+const codecForPublicAccount = (): Codec =>
+ buildCodecForObject()
+ .property("account_name", codecForString())
+ .property("balance", codecForBalance())
+ .property("payto_uri", codecForPaytoURI())
+ .build("TalerCorebankApi.PublicAccount")
+
+export const codecForPublicAccountsResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("public_accounts", codecForList(codecForPublicAccount()))
+ .build("TalerCorebankApi.PublicAccountsResponse")
+
+
+export const codecForAccountMinimalData =
+ (): Codec =>
+ buildCodecForObject()
+ .property("balance", codecForBalance())
+ .property("debit_threshold", codecForAmountString())
+ .property("name", codecForString())
+ .property("username", codecForString())
+ .build("TalerCorebankApi.AccountMinimalData")
+
+export const codecForListBankAccountsResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("accounts", codecForList(codecForAccountMinimalData()))
+ .build("TalerCorebankApi.ListBankAccountsResponse")
+
+export const codecForAccountData =
+ (): Codec =>
+ buildCodecForObject()
+ .property("name", codecForString())
+ .property("balance", codecForBalance())
+ .property("payto_uri", codecForPaytoURI())
+ .property("debit_threshold", codecForAmountString())
+ .property("contact_data", codecOptional(codecForChallengeContactData()))
+ .property("cashout_payto_uri", codecOptional(codecForPaytoURI()))
+ .build("TalerCorebankApi.AccountData")
+
+
+export const codecForChallengeContactData =
+ (): Codec =>
+ buildCodecForObject()
+ .property("email", codecOptional(codecForString()))
+ .property("phone", codecOptional(codecForString()))
+ .build("TalerCorebankApi.ChallengeContactData")
+
+export const codecForBankAccountTransactionsResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("transactions", codecForList(codecForBankAccountTransactionInfo()))
+ .build("TalerCorebankApi.BankAccountTransactionsResponse");
+
+export const codecForBankAccountTransactionInfo =
+ (): Codec =>
+ buildCodecForObject()
+ .property("amount", codecForAmountString())
+ .property("creditor_payto_uri", codecForPaytoURI())
+ .property("date", codecForTimestamp)
+ .property("debtor_payto_uri", codecForPaytoURI())
+ .property("direction", codecForEither(codecForConstString("debit"), codecForConstString("credit")))
+ .property("row_id", codecForNumber())
+ .property("subject", codecForString())
+ .build("TalerCorebankApi.BankAccountTransactionInfo");
+
+export const codecForBankAccountCreateWithdrawalResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("taler_withdraw_uri", codecForTalerWithdrawalURI())
+ .property("withdrawal_id", codecForString())
+ .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse");
+
+export const codecForBankAccountGetWithdrawalResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("aborted", codecForBoolean())
+ .property("amount", codecForAmountString())
+ .property("confirmation_done", codecForBoolean())
+ .property("selected_exchange_account", codecOptional(codecForString()))
+ .property("selected_reserve_pub", codecOptional(codecForString()))
+ .property("selection_done", (codecForBoolean()))
+ .build("TalerCorebankApi.BankAccountGetWithdrawalResponse");
+
+export const codecForCashoutPending =
+ (): Codec =>
+ buildCodecForObject()
+ .property("cashout_id", codecForString())
+ .build("TalerCorebankApi.CashoutPending");
+
+export const codecForCashoutConversionResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("amount_credit", codecForAmountString())
+ .property("amount_debit", codecForAmountString())
+ .build("TalerCorebankApi.CashoutConversionResponse");
+
+export const codecForCashouts =
+ (): Codec =>
+ buildCodecForObject()
+ .property("cashouts", codecForList(codecForCashoutInfo()))
+ .build("TalerCorebankApi.Cashouts");
+
+export const codecForCashoutInfo =
+ (): Codec =>
+ buildCodecForObject()
+ .property("cashout_id", codecForString())
+ .property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed"),))
+ .build("TalerCorebankApi.CashoutInfo");
+
+export const codecForGlobalCashouts =
+ (): Codec =>
+ buildCodecForObject()
+ .property("cashouts", codecForList(codecForGlobalCashoutInfo()))
+ .build("TalerCorebankApi.GlobalCashouts");
+
+export const codecForGlobalCashoutInfo =
+ (): Codec =>
+ buildCodecForObject()
+ .property("cashout_id", codecForString())
+ .property("username", codecForString())
+ .property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed"),))
+ .build("TalerCorebankApi.GlobalCashoutInfo");
+
+export const codecForCashoutStatusResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("amount_credit", codecForAmountString())
+ .property("amount_debit", codecForAmountString())
+ .property("confirmation_time", codecForTimestamp)
+ .property("creation_time", codecForTimestamp)
+ .property("credit_payto_uri", codecForPaytoURI())
+ .property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed")))
+ .property("subject", codecForString())
+ .build("TalerCorebankApi.CashoutStatusResponse");
+
+export const codecForConversionRatesResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("buy_at_ratio", codecForDecimalNumber())
+ .property("buy_in_fee", codecForDecimalNumber())
+ .property("sell_at_ratio", codecForDecimalNumber())
+ .property("sell_out_fee", codecForDecimalNumber())
+ .build("TalerCorebankApi.ConversionRatesResponse");
+
+export const codecForMonitorResponse =
+ (): Codec =>
+ buildCodecForObject()
+ .property("cashinCount", codecForNumber())
+ .property("cashinExternalVolume", codecForAmountString())
+ .property("cashoutCount", codecForNumber())
+ .property("cashoutExternalVolume", codecForAmountString())
+ .property("talerPayoutCount", codecForNumber())
+ .property("talerPayoutInternalVolume", codecForAmountString())
+ .build("TalerCorebankApi.MonitorResponse");
+
+// export const codecFor =
+// (): Codec =>
+// buildCodecForObject()
+// .property("", codecForString())
+// .build("TalerCorebankApi.PublicAccountsResponse");
+
+type EmailAddress = string;
+type PhoneNumber = string;
+type DecimalNumber = string;
+
+const codecForPaytoURI = codecForString
+const codecForTalerWithdrawalURI = codecForString
+const codecForDecimalNumber = codecForString
+
+enum TanChannel {
+ SMS = "sms",
+ EMAIL = "email",
+ FILE = "file"
+}
+
+
+export namespace TalerCorebankApi {
+
+ export interface Config {
+ // Name of this API, always "circuit".
+ name: string;
+ // API version in the form $n:$n:$n
+ version: string;
+
+ // If 'true', the server provides local currency
+ // conversion support.
+ // If missing or false, some parts of the API
+ // are not supported and return 404.
+ have_cashout?: boolean;
+
+ // How the bank SPA should render the currency.
+ currency: CurrencySpecification;
+
+ // Fiat currency. That is the currency in which
+ // cash-out operations ultimately wire money.
+ // Only applicable if have_cashout=true.
+ fiat_currency?: CurrencySpecification;
+ }
+
+ export interface BankAccountCreateWithdrawalRequest {
+ // Amount to withdraw.
+ amount: Amount;
+ }
+ export interface BankAccountCreateWithdrawalResponse {
+ // ID of the withdrawal, can be used to view/modify the withdrawal operation.
+ withdrawal_id: string;
+
+ // URI that can be passed to the wallet to initiate the withdrawal.
+ taler_withdraw_uri: string;
+ }
+ export interface BankAccountGetWithdrawalResponse {
+ // Amount that will be withdrawn with this withdrawal operation.
+ amount: Amount;
+
+ // Was the withdrawal aborted?
+ aborted: boolean;
+
+ // Has the withdrawal been confirmed by the bank?
+ // The wire transfer for a withdrawal is only executed once
+ // both confirmation_done is true and selection_done is true.
+ confirmation_done: boolean;
+
+ // Did the wallet select reserve details?
+ selection_done: boolean;
+
+ // Reserve public key selected by the exchange,
+ // only non-null if selection_done is true.
+ selected_reserve_pub: string | undefined;
+
+ // Exchange account selected by the wallet, or by the bank
+ // (with the default exchange) in case the wallet did not provide one
+ // through the Integration API.
+ selected_exchange_account: string | undefined;
+ }
+
+ export interface BankAccountTransactionsResponse {
+ transactions: BankAccountTransactionInfo[];
+ }
+
+ export interface BankAccountTransactionInfo {
+ creditor_payto_uri: string;
+ debtor_payto_uri: string;
+
+ amount: Amount;
+ direction: "debit" | "credit";
+
+ subject: string;
+
+ // Transaction unique ID. Matches
+ // $transaction_id from the URI.
+ row_id: number;
+ date: Timestamp;
+ }
+
+ export interface CreateBankAccountTransactionCreate {
+ // Address in the Payto format of the wire transfer receiver.
+ // It needs at least the 'message' query string parameter.
+ payto_uri: string;
+
+ // Transaction amount (in the $currency:x.y format), optional.
+ // However, when not given, its value must occupy the 'amount'
+ // query string parameter of the 'payto' field. In case it
+ // is given in both places, the paytoUri's takes the precedence.
+ amount?: string;
+ }
+
+ export interface RegisterAccountRequest {
+ // Username
+ username: string;
+
+ // Password.
+ password: string;
+
+ // Legal name of the account owner
+ name: string;
+
+ // Defaults to false.
+ is_public?: boolean;
+
+ // Is this a taler exchange account?
+ // If true:
+ // - incoming transactions to the account that do not
+ // have a valid reserve public key are automatically
+ // - the account provides the taler-wire-gateway-api endpoints
+ // Defaults to false.
+ is_taler_exchange?: boolean;
+
+ // Addresses where to send the TAN for transactions.
+ // Currently only used for cashouts.
+ // If missing, cashouts will fail.
+ // In the future, might be used for other transactions
+ // as well.
+ challenge_contact_data?: ChallengeContactData;
+
+ // 'payto' address pointing a bank account
+ // external to the libeufin-bank.
+ // Payments will be sent to this bank account
+ // when the user wants to convert the local currency
+ // back to fiat currency outside libeufin-bank.
+ cashout_payto_uri?: string;
+
+ // Internal payto URI of this bank account.
+ // Used mostly for testing.
+ internal_payto_uri?: string;
+ }
+ export interface ChallengeContactData {
+
+ // E-Mail address
+ email?: EmailAddress;
+
+ // Phone number.
+ phone?: PhoneNumber;
+ }
+
+ export interface AccountReconfiguration {
+
+ // Addresses where to send the TAN for transactions.
+ // Currently only used for cashouts.
+ // If missing, cashouts will fail.
+ // In the future, might be used for other transactions
+ // as well.
+ challenge_contact_data?: ChallengeContactData;
+
+ // 'payto' address pointing a bank account
+ // external to the libeufin-bank.
+ // Payments will be sent to this bank account
+ // when the user wants to convert the local currency
+ // back to fiat currency outside libeufin-bank.
+ cashout_address?: string;
+
+ // Legal name associated with $username.
+ // When missing, the old name is kept.
+ name?: string;
+
+ // If present, change the is_exchange configuration.
+ // See RegisterAccountRequest
+ is_exchange?: boolean;
+ }
+
+
+ export interface AccountPasswordChange {
+
+ // New password.
+ new_password: string;
+ }
+
+ export interface PublicAccountsResponse {
+ public_accounts: PublicAccount[];
+ }
+ export interface PublicAccount {
+ payto_uri: string;
+
+ balance: Balance;
+
+ // The account name (=username) of the
+ // libeufin-bank account.
+ account_name: string;
+ }
+
+ export interface ListBankAccountsResponse {
+ accounts: AccountMinimalData[];
+ }
+ export interface Balance {
+ amount: Amount;
+ credit_debit_indicator: "credit" | "debit";
+ }
+ export interface AccountMinimalData {
+ // Username
+ username: string;
+
+ // Legal name of the account owner.
+ name: string;
+
+ // current balance of the account
+ balance: Balance;
+
+ // Number indicating the max debit allowed for the requesting user.
+ debit_threshold: Amount;
+ }
+
+ export interface AccountData {
+ // Legal name of the account owner.
+ name: string;
+
+ // Available balance on the account.
+ balance: Balance;
+
+ // payto://-URI of the account.
+ payto_uri: string;
+
+ // Number indicating the max debit allowed for the requesting user.
+ debit_threshold: Amount;
+
+ contact_data?: ChallengeContactData;
+
+ // 'payto' address pointing the bank account
+ // where to send cashouts. This field is optional
+ // because not all the accounts are required to participate
+ // in the merchants' circuit. One example is the exchange:
+ // that never cashouts. Registering these accounts can
+ // be done via the access API.
+ cashout_payto_uri?: string;
+ }
+
+
+ export interface CashoutRequest {
+
+ // Optional subject to associate to the
+ // cashout operation. This data will appear
+ // as the incoming wire transfer subject in
+ // the user's external bank account.
+ subject?: string;
+
+ // That is the plain amount that the user specified
+ // to cashout. Its $currency is the (regional) currency of the
+ // bank instance.
+ amount_debit: Amount;
+
+ // That is the amount that will effectively be
+ // transferred by the bank to the user's bank
+ // account, that is external to the regional currency.
+ // It is expressed in the fiat currency and
+ // is calculated after the cashout fee and the
+ // exchange rate. See the /cashout-rates call.
+ // The client needs to calculate this amount
+ // correctly based on the amount_debit and the cashout rate,
+ // otherwise the request will fail.
+ amount_credit: Amount;
+
+ // Which channel the TAN should be sent to. If
+ // this field is missing, it defaults to SMS.
+ // The default choice prefers to change the communication
+ // channel respect to the one used to issue this request.
+ tan_channel?: TanChannel;
+ }
+
+ export interface CashoutPending {
+ // ID identifying the operation being created
+ // and now waiting for the TAN confirmation.
+ cashout_id: string;
+ }
+
+ export interface CashoutConfirmRequest {
+ // the TAN that confirms $CASHOUT_ID.
+ tan: string;
+ }
+
+ export interface CashoutConversionResponse {
+ // Amount that the user will get deducted from their regional
+ // bank account, according to the 'amount_credit' value.
+ amount_debit: Amount;
+ // Amount that the user will receive in their fiat
+ // bank account, according to 'amount_debit'.
+ amount_credit: Amount;
+ }
+
+ export interface Cashouts {
+ // Every string represents a cash-out operation ID.
+ cashouts: CashoutInfo[];
+ }
+
+ export interface CashoutInfo {
+ cashout_id: string;
+ status: "pending" | "confirmed";
+ }
+ export interface GlobalCashouts {
+ // Every string represents a cash-out operation ID.
+ cashouts: GlobalCashoutInfo[];
+ }
+ export interface GlobalCashoutInfo {
+ cashout_id: string;
+ username: string;
+ status: "pending" | "confirmed";
+ }
+
+ export interface CashoutStatusResponse {
+ status: "pending" | "confirmed";
+
+ // Amount debited to the internal
+ // regional currency bank account.
+ amount_debit: Amount;
+
+ // Amount credited to the external bank account.
+ amount_credit: Amount;
+
+ // Transaction subject.
+ subject: string;
+
+ // Fiat bank account that will receive the cashed out amount.
+ // Specified as a payto URI.
+ credit_payto_uri: string;
+
+ // Time when the cashout was created.
+ creation_time: Timestamp;
+
+ // Time when the cashout was confirmed via its TAN.
+ // Missing when the operation wasn't confirmed yet.
+ confirmation_time?: Timestamp;
+ }
+
+ export interface ConversionRatesResponse {
+
+ // Exchange rate to buy the local currency from the external one
+ buy_at_ratio: DecimalNumber;
+
+ // Exchange rate to sell the local currency for the external one
+ sell_at_ratio: DecimalNumber;
+
+ // Fee to subtract after applying the buy ratio.
+ buy_in_fee: DecimalNumber;
+
+ // Fee to subtract after applying the sell ratio.
+ sell_out_fee: DecimalNumber;
+ }
+
+ export enum MonitorTimeframeParam{
+ hour, day, month, year, decade,
+ }
+
+ export interface MonitorResponse {
+
+ // This number identifies how many cashin operations
+ // took place in the timeframe specified in the request.
+ // This number corresponds to how many withdrawals have
+ // been initiated by a wallet owner. Note: wallet owners
+ // are NOT required to be customers of the libeufin-bank.
+ cashinCount: number;
+
+ // This amount accounts how much external currency has been
+ // spent to withdraw Taler coins in the internal currency.
+ // The exact amount of internal currency being created can be
+ // calculated using the advertised conversion rates.
+ cashinExternalVolume: Amount;
+
+ // This number identifies how many cashout operations were
+ // confirmed in the timeframe speficied in the request.
+ cashoutCount: number;
+
+ // This amount corresponds to how much *external* currency was
+ // paid by the libeufin-bank administrator to fulfill all the
+ // confirmed cashouts related to the timeframe specified in the
+ // request.
+ cashoutExternalVolume: Amount;
+
+ // This number identifies how many payments were made by a
+ // Taler exchange to a merchant bank account in the internal
+ // currency, in the timeframe specified in the request.
+ talerPayoutCount: number;
+
+ // This amount accounts the overall *internal* currency that
+ // has been paid by a Taler exchange to a merchant internal
+ // bank account, in the timeframe specified in the request.
+ talerPayoutInternalVolume: Amount;
+ }
+
+
+}
diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts
new file mode 100644
index 000000000..ecb4d14c4
--- /dev/null
+++ b/packages/taler-util/src/http-client/utils.ts
@@ -0,0 +1,62 @@
+import { base64FromArrayBuffer } from "../base64.js";
+import { stringToBytes } from "../taler-crypto.js";
+import { AccessToken, TalerAuthentication } from "./types.js";
+
+/**
+ * Helper function to generate the "Authorization" HTTP header.
+ */
+export function makeBasicAuthHeader(username: string, password: string): string {
+ const auth = `${username}:${password}`;
+ const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth));
+ return `Basic ${authEncoded}`;
+}
+
+/**
+ * rfc8959
+ * @param token
+ * @returns
+ */
+export function makeBearerTokenAuthHeader(token: AccessToken): string {
+ return `Bearer secret-token:${token}`;
+}
+
+/**
+ * https://bugs.gnunet.org/view.php?id=7949
+ */
+export function addPaginationParams(url: URL, pagination?: PaginationParams) {
+ if (!pagination) return;
+ if (pagination.timoutMs) {
+ url.searchParams.set("long_poll_ms", String(pagination.timoutMs))
+ }
+ if (pagination.offset) {
+ url.searchParams.set("start", pagination.offset)
+ }
+ if (pagination.limit) {
+ url.searchParams.set("delta", String(pagination.limit))
+ }
+}
+
+export type UserAndPassword = {
+ username: string,
+ password: string,
+}
+
+export type UserAndToken = {
+ username: string,
+ token: AccessToken,
+}
+
+export type PaginationParams = {
+ /**
+ * row identifier as the starting point of the query
+ */
+ offset?: string,
+ /**
+ * max number of element in the result response
+ */
+ limit?: number,
+ /**
+ * milliseconds the server should wait for at least one result to be shown
+ */
+ timoutMs?: number,
+}
diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts
index f25705545..da2fbb9da 100644
--- a/packages/taler-util/src/http-common.ts
+++ b/packages/taler-util/src/http-common.ts
@@ -50,7 +50,7 @@ export interface HttpResponse {
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
export interface HttpRequestOptions {
- method?: "POST" | "PUT" | "GET" | "DELETE";
+ method?: "POST" | "PATCH" | "PUT" | "GET" | "DELETE";
headers?: { [name: string]: string };
/**