gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: wip: http-client


From: gnunet
Subject: [taler-wallet-core] branch master updated: wip: http-client
Date: Sat, 14 Oct 2023 04:59:54 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 36b7918a7 wip: http-client
36b7918a7 is described below

commit 36b7918a794c5ada65837ca2f617eaaa2f01460b
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri Oct 13 23:59:26 2023 -0300

    wip: http-client
---
 packages/taler-util/src/http-client/core-bank.ts | 523 +++++++++++++++++
 packages/taler-util/src/http-client/types.ts     | 703 +++++++++++++++++++++++
 packages/taler-util/src/http-client/utils.ts     |  62 ++
 packages/taler-util/src/http-common.ts           |   2 +-
 4 files changed, 1289 insertions(+), 1 deletion(-)

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 <http://www.gnu.org/licenses/>
+ */
+
+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, codec [...]
+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<TalerAuthentication.TokenSuccessResponse> {
+    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<void> {
+    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<TalerCorebankApi.Config> {
+    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<void> {
+    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<void> {
+    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<void> {
+    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<void> {
+    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<TalerCorebankApi.PublicAccountsResponse> {
+    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<TalerCorebankApi.ListBankAccountsResponse> {
+    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<TalerCorebankApi.AccountData> {
+    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<TalerCorebankApi.BankAccountTransactionsResponse> {
+    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<TalerCorebankApi.BankAccountTransactionInfo> {
+    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<void> {
+    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<TalerCorebankApi.BankAccountCreateWithdrawalResponse> {
+    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<TalerCorebankApi.BankAccountGetWithdrawalResponse> {
+    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<void> {
+    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<void> {
+    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<TalerCorebankApi.CashoutPending> {
+    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<void> {
+    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<void> {
+    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<TalerCorebankApi.CashoutConversionResponse> {
+    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<TalerCorebankApi.Cashouts> {
+    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<TalerCorebankApi.GlobalCashouts> {
+    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<TalerCorebankApi.CashoutStatusResponse> {
+    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<TalerCorebankApi.ConversionRatesResponse> {
+    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<TalerCorebankApi.MonitorResponse> {
+    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<AccessToken>;
+export const codecForTokenSuccessResponse =
+  (): Codec<TalerAuthentication.TokenSuccessResponse> =>
+    buildCodecForObject<TalerAuthentication.TokenSuccessResponse>()
+      .property("access_token", codecForAccessToken())
+      .property("expiration", codecForTimestamp)
+      .build("TalerAuthentication.TokenSuccessResponse")
+
+export const codecForCurrencySpecificiation =
+  (): Codec<CurrencySpecification> =>
+    buildCodecForObject<CurrencySpecification>()
+      .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<TalerCorebankApi.Config> =>
+    buildCodecForObject<TalerCorebankApi.Config>()
+      .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<TalerCorebankApi.Balance> =>
+  buildCodecForObject<TalerCorebankApi.Balance>()
+    .property("amount", codecForAmountString())
+    .property("credit_debit_indicator", 
codecForEither(codecForConstString("credit"), codecForConstString("debit")))
+    .build("TalerCorebankApi.Balance")
+
+const codecForPublicAccount = (): Codec<TalerCorebankApi.PublicAccount> =>
+  buildCodecForObject<TalerCorebankApi.PublicAccount>()
+    .property("account_name", codecForString())
+    .property("balance", codecForBalance())
+    .property("payto_uri", codecForPaytoURI())
+    .build("TalerCorebankApi.PublicAccount")
+
+export const codecForPublicAccountsResponse =
+  (): Codec<TalerCorebankApi.PublicAccountsResponse> =>
+    buildCodecForObject<TalerCorebankApi.PublicAccountsResponse>()
+      .property("public_accounts", codecForList(codecForPublicAccount()))
+      .build("TalerCorebankApi.PublicAccountsResponse")
+
+
+export const codecForAccountMinimalData =
+  (): Codec<TalerCorebankApi.AccountMinimalData> =>
+    buildCodecForObject<TalerCorebankApi.AccountMinimalData>()
+      .property("balance", codecForBalance())
+      .property("debit_threshold", codecForAmountString())
+      .property("name", codecForString())
+      .property("username", codecForString())
+      .build("TalerCorebankApi.AccountMinimalData")
+
+export const codecForListBankAccountsResponse =
+  (): Codec<TalerCorebankApi.ListBankAccountsResponse> =>
+    buildCodecForObject<TalerCorebankApi.ListBankAccountsResponse>()
+      .property("accounts", codecForList(codecForAccountMinimalData()))
+      .build("TalerCorebankApi.ListBankAccountsResponse")
+
+export const codecForAccountData =
+  (): Codec<TalerCorebankApi.AccountData> =>
+    buildCodecForObject<TalerCorebankApi.AccountData>()
+      .property("name", codecForString())
+      .property("balance", codecForBalance())
+      .property("payto_uri", codecForPaytoURI())
+      .property("debit_threshold", codecForAmountString())
+      .property("contact_data", codecOptional(codecForChallengeContactData()))
+      .property("cashout_payto_uri", codecOptional(codecForPaytoURI()))
+      .build("TalerCorebankApi.AccountData")
+
+
+export const codecForChallengeContactData =
+  (): Codec<TalerCorebankApi.ChallengeContactData> =>
+    buildCodecForObject<TalerCorebankApi.ChallengeContactData>()
+      .property("email", codecOptional(codecForString()))
+      .property("phone", codecOptional(codecForString()))
+      .build("TalerCorebankApi.ChallengeContactData")
+
+export const codecForBankAccountTransactionsResponse =
+  (): Codec<TalerCorebankApi.BankAccountTransactionsResponse> =>
+    buildCodecForObject<TalerCorebankApi.BankAccountTransactionsResponse>()
+      .property("transactions", 
codecForList(codecForBankAccountTransactionInfo()))
+      .build("TalerCorebankApi.BankAccountTransactionsResponse");
+
+export const codecForBankAccountTransactionInfo =
+  (): Codec<TalerCorebankApi.BankAccountTransactionInfo> =>
+    buildCodecForObject<TalerCorebankApi.BankAccountTransactionInfo>()
+      .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<TalerCorebankApi.BankAccountCreateWithdrawalResponse> =>
+    buildCodecForObject<TalerCorebankApi.BankAccountCreateWithdrawalResponse>()
+      .property("taler_withdraw_uri", codecForTalerWithdrawalURI())
+      .property("withdrawal_id", codecForString())
+      .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse");
+
+export const codecForBankAccountGetWithdrawalResponse =
+  (): Codec<TalerCorebankApi.BankAccountGetWithdrawalResponse> =>
+    buildCodecForObject<TalerCorebankApi.BankAccountGetWithdrawalResponse>()
+      .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<TalerCorebankApi.CashoutPending> =>
+    buildCodecForObject<TalerCorebankApi.CashoutPending>()
+      .property("cashout_id", codecForString())
+      .build("TalerCorebankApi.CashoutPending");
+
+export const codecForCashoutConversionResponse =
+  (): Codec<TalerCorebankApi.CashoutConversionResponse> =>
+    buildCodecForObject<TalerCorebankApi.CashoutConversionResponse>()
+      .property("amount_credit", codecForAmountString())
+      .property("amount_debit", codecForAmountString())
+      .build("TalerCorebankApi.CashoutConversionResponse");
+
+export const codecForCashouts =
+  (): Codec<TalerCorebankApi.Cashouts> =>
+    buildCodecForObject<TalerCorebankApi.Cashouts>()
+      .property("cashouts", codecForList(codecForCashoutInfo()))
+      .build("TalerCorebankApi.Cashouts");
+
+export const codecForCashoutInfo =
+  (): Codec<TalerCorebankApi.CashoutInfo> =>
+    buildCodecForObject<TalerCorebankApi.CashoutInfo>()
+      .property("cashout_id", codecForString())
+      .property("status", codecForEither(codecForConstString("pending"), 
codecForConstString("confirmed"),))
+      .build("TalerCorebankApi.CashoutInfo");
+
+export const codecForGlobalCashouts =
+  (): Codec<TalerCorebankApi.GlobalCashouts> =>
+    buildCodecForObject<TalerCorebankApi.GlobalCashouts>()
+      .property("cashouts", codecForList(codecForGlobalCashoutInfo()))
+      .build("TalerCorebankApi.GlobalCashouts");
+
+export const codecForGlobalCashoutInfo =
+  (): Codec<TalerCorebankApi.GlobalCashoutInfo> =>
+    buildCodecForObject<TalerCorebankApi.GlobalCashoutInfo>()
+    .property("cashout_id", codecForString())
+    .property("username", codecForString())
+    .property("status", codecForEither(codecForConstString("pending"), 
codecForConstString("confirmed"),))
+      .build("TalerCorebankApi.GlobalCashoutInfo");
+
+export const codecForCashoutStatusResponse =
+  (): Codec<TalerCorebankApi.CashoutStatusResponse> =>
+    buildCodecForObject<TalerCorebankApi.CashoutStatusResponse>()
+    .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<TalerCorebankApi.ConversionRatesResponse> =>
+    buildCodecForObject<TalerCorebankApi.ConversionRatesResponse>()
+    .property("buy_at_ratio", codecForDecimalNumber())
+    .property("buy_in_fee", codecForDecimalNumber())
+    .property("sell_at_ratio", codecForDecimalNumber())
+    .property("sell_out_fee", codecForDecimalNumber())
+    .build("TalerCorebankApi.ConversionRatesResponse");
+
+export const codecForMonitorResponse =
+  (): Codec<TalerCorebankApi.MonitorResponse> =>
+    buildCodecForObject<TalerCorebankApi.MonitorResponse>()
+    .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<TalerCorebankApi.PublicAccountsResponse> =>
+//     buildCodecForObject<TalerCorebankApi.PublicAccountsResponse>()
+//       .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 };
 
   /**

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]