gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: wallet-core: implement and te


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: implement and test currency conversion withdrawals
Date: Wed, 22 Nov 2023 17:08:44 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new bd37a0b04 wallet-core: implement and test currency conversion 
withdrawals
bd37a0b04 is described below

commit bd37a0b04123d734e1e3fae105f0d9c24279629f
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Nov 22 15:20:10 2023 +0100

    wallet-core: implement and test currency conversion withdrawals
---
 packages/taler-harness/src/harness/harness.ts      |  39 +-
 .../integrationtests/test-withdrawal-conversion.ts |  34 +-
 packages/taler-util/src/http-client/types.ts       | 534 +++++++++------------
 packages/taler-util/src/taler-types.ts             |  41 +-
 packages/taler-util/src/transactions-types.ts      |   5 +
 packages/taler-util/src/wallet-types.ts            |  24 +-
 .../src/crypto/cryptoImplementation.ts             |   2 +-
 packages/taler-wallet-core/src/db.ts               |   7 +-
 .../src/operations/transactions.ts                 |   1 +
 .../taler-wallet-core/src/operations/withdraw.ts   | 129 ++++-
 packages/taler-wallet-core/src/wallet.ts           |   1 +
 11 files changed, 474 insertions(+), 343 deletions(-)

diff --git a/packages/taler-harness/src/harness/harness.ts 
b/packages/taler-harness/src/harness/harness.ts
index 37e0b02a7..242bf2207 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -928,6 +928,8 @@ export class ExchangeService implements 
ExchangeServiceInterface {
 
   private currentTimetravelOffsetMs: number | undefined;
 
+  private exchangeBankAccounts: HarnessExchangeBankAccount[] = [];
+
   setTimetravel(t: number | undefined): void {
     if (this.isRunning()) {
       throw Error("can't set time travel while the exchange is running");
@@ -1132,6 +1134,7 @@ export class ExchangeService implements 
ExchangeServiceInterface {
     localName: string,
     exchangeBankAccount: HarnessExchangeBankAccount,
   ): Promise<void> {
+    this.exchangeBankAccounts.push(exchangeBankAccount);
     const config = Configuration.load(this.configFilename);
     config.setString(
       `exchange-account-${localName}`,
@@ -1277,32 +1280,32 @@ export class ExchangeService implements 
ExchangeServiceInterface {
       ["-c", this.configFilename, "download", "sign", "upload"],
     );
 
-    const accounts: string[] = [];
     const accountTargetTypes: Set<string> = new Set();
 
-    const config = Configuration.load(this.configFilename);
-    for (const sectionName of config.getSectionNames()) {
-      if (sectionName.startsWith("EXCHANGE-ACCOUNT-")) {
-        const paytoUri = config.getString(sectionName, "payto_uri").required();
-        const p = parsePaytoUri(paytoUri);
-        if (!p) {
-          throw Error(`invalid payto uri in exchange config: ${paytoUri}`);
-        }
-        accountTargetTypes.add(p?.targetType);
-        accounts.push(paytoUri);
+    for (const acct of this.exchangeBankAccounts) {
+      const paytoUri = acct.accountPaytoUri;
+      const p = parsePaytoUri(paytoUri);
+      if (!p) {
+        throw Error(`invalid payto uri in exchange config: ${paytoUri}`);
+      }
+      accountTargetTypes.add(p?.targetType);
+      const optArgs: string[] = [];
+      if (acct.conversionUrl != null) {
+        optArgs.push("conversion-url", acct.conversionUrl);
       }
-    }
-
-    const accountsDescription = accounts.map((acc) => ` * ${acc}`).join("\n");
-    logger.info("configuring bank accounts:");
-    logger.info(accountsDescription);
 
-    for (const acc of accounts) {
       await runCommand(
         this.globalState,
         "exchange-offline",
         "taler-exchange-offline",
-        ["-c", this.configFilename, "enable-account", acc, "upload"],
+        [
+          "-c",
+          this.configFilename,
+          "enable-account",
+          paytoUri,
+          ...optArgs,
+          "upload",
+        ],
       );
     }
 
diff --git 
a/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts 
b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
index 2a9dd5800..6fc403be4 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
@@ -23,7 +23,9 @@ import {
   Duration,
   Logger,
   TalerCorebankApiClient,
+  TransactionType,
   WireGatewayApiClient,
+  WithdrawalType,
   j2s,
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@@ -184,16 +186,21 @@ export async function runWithdrawalConversionTest(t: 
GlobalTestState) {
     exchangeBaseUrl: exchange.baseUrl,
   });
 
-  const infoRes = walletClient.call(
+  const infoRes = await walletClient.call(
     WalletApiOperation.GetWithdrawalDetailsForAmount,
     {
       exchangeBaseUrl: exchange.baseUrl,
-      amount: "EXTCOIN:20" as AmountString,
+      amount: "TESTKUDOS:20" as AmountString,
     },
   );
 
   console.log(`withdrawal details: ${j2s(infoRes)}`);
 
+  t.assertAmountEquals(
+    infoRes.withdrawalAccountList[0].transferAmount,
+    "FOO:123",
+  );
+
   const tStart = AbsoluteTime.now();
 
   logger.info("starting AcceptManualWithdrawal request");
@@ -210,6 +217,29 @@ export async function runWithdrawalConversionTest(t: 
GlobalTestState) {
   logger.info("AcceptManualWithdrawal finished");
   logger.info(`result: ${j2s(wres)}`);
 
+  t.assertAmountEquals(
+    wres.withdrawalAccountsList[0].transferAmount,
+    "FOO:123",
+  );
+
+  const txInfo = await walletClient.call(
+    WalletApiOperation.GetTransactionById,
+    {
+      transactionId: wres.transactionId,
+    },
+  );
+
+  t.assertDeepEqual(txInfo.type, TransactionType.Withdrawal);
+  t.assertDeepEqual(
+    txInfo.withdrawalDetails.type,
+    WithdrawalType.ManualTransfer,
+  );
+  t.assertTrue(!!txInfo.withdrawalDetails.exchangeCreditAccounts);
+  t.assertDeepEqual(
+    txInfo.withdrawalDetails.exchangeCreditAccounts[0].transferAmount,
+    "FOO:123",
+  );
+
   // Check that the request did not go into long-polling.
   const duration = AbsoluteTime.difference(tStart, AbsoluteTime.now());
   if (typeof duration.d_ms !== "number" || duration.d_ms > 5 * 1000) {
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index d50a0ea90..3ef0ff76c 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -1,20 +1,32 @@
 import { codecForAmountString } from "../amounts.js";
-import { Codec, buildCodecForObject, buildCodecForUnion, codecForAny, 
codecForBoolean, codecForConstString, codecForEither, codecForList, 
codecForMap, codecForNumber, codecForString, codecOptional } from "../codec.js";
+import {
+  Codec,
+  buildCodecForObject,
+  buildCodecForUnion,
+  codecForAny,
+  codecForBoolean,
+  codecForConstString,
+  codecForEither,
+  codecForList,
+  codecForMap,
+  codecForNumber,
+  codecForString,
+  codecOptional,
+} from "../codec.js";
 import { PaytoString, codecForPaytoString } from "../payto.js";
 import { AmountString } from "../taler-types.js";
 import { TalerActionString, codecForTalerActionString } from "../taleruri.js";
 import { codecForTimestamp } from "../time.js";
 
-
 export type UserAndPassword = {
-  username: string,
-  password: string,
-}
+  username: string;
+  password: string;
+};
 
 export type UserAndToken = {
-  username: string,
-  token: AccessToken,
-}
+  username: string;
+  token: AccessToken;
+};
 
 declare const opaque_OfficerAccount: unique symbol;
 export type LockedAccount = string & { [opaque_OfficerAccount]: true };
@@ -25,32 +37,30 @@ export type OfficerId = string & { [opaque_OfficerId]: true 
};
 declare const opaque_OfficerSigningKey: unique symbol;
 export type SigningKey = Uint8Array & { [opaque_OfficerSigningKey]: true };
 
-
 export interface OfficerAccount {
   id: OfficerId;
   signingKey: SigningKey;
 }
 
-
 export type PaginationParams = {
   /**
    * row identifier as the starting point of the query
    */
-  offset?: string,
+  offset?: string;
   /**
    * max number of element in the result response
    * always greater than 0
    */
-  limit?: number,
+  limit?: number;
   /**
    * milliseconds the server should wait for at least one result to be shown
    */
-  timoutMs?: number,
+  timoutMs?: number;
   /**
    * order
    */
-  order: "asc" | "dec"
-}
+  order: "asc" | "dec";
+};
 
 ///
 /// HASH
@@ -148,7 +158,6 @@ type SafeUint64 = number;
 // Invalid strings will be rejected by the wallet.
 type ImageDataUrl = string;
 
-
 type WadId = string;
 
 interface Timestamp {
@@ -166,8 +175,8 @@ interface RelativeTime {
 }
 
 export interface LoginToken {
-  token: AccessToken,
-  expiration: Timestamp,
+  token: AccessToken;
+  expiration: Timestamp;
 }
 
 declare const __ac_token: unique symbol;
@@ -175,14 +184,12 @@ export type AccessToken = string & {
   [__ac_token]: true;
 };
 
-
 declare const __officer_signature: unique symbol;
 export type OfficerSignature = string & {
   [__officer_signature]: true;
 };
 
 export namespace TalerAuthentication {
-
   export interface TokenRequest {
     // Service-defined scope for the token.
     // Typical scopes would be "readonly" or "readwrite".
@@ -213,7 +220,6 @@ export namespace TalerAuthentication {
 
 // DD51 https://docs.taler.net/design-documents/051-fractional-digits.html
 export interface CurrencySpecification {
-
   // Name of the currency.
   name: string;
 
@@ -240,7 +246,7 @@ export const codecForTokenSuccessResponse =
     buildCodecForObject<TalerAuthentication.TokenSuccessResponse>()
       .property("access_token", codecForAccessToken())
       .property("expiration", codecForTimestamp)
-      .build("TalerAuthentication.TokenSuccessResponse")
+      .build("TalerAuthentication.TokenSuccessResponse");
 
 export const codecForCurrencySpecificiation =
   (): Codec<CurrencySpecification> =>
@@ -250,27 +256,26 @@ export const codecForCurrencySpecificiation =
       .property("num_fractional_normal_digits", codecForNumber())
       .property("num_fractional_trailing_zero_digits", codecForNumber())
       .property("alt_unit_names", codecForMap(codecForString()))
-      .build("CurrencySpecification")
+      .build("CurrencySpecification");
 
 export const codecForIntegrationBankConfig =
   (): Codec<TalerCorebankApi.IntegrationConfig> =>
     buildCodecForObject<TalerCorebankApi.IntegrationConfig>()
       .property("name", codecForConstString("taler-bank-integration"))
       .property("version", codecForString())
-      .property("currency", codecForCurrencySpecificiation())
-      .build("TalerCorebankApi.IntegrationConfig")
-
-export const codecForCoreBankConfig =
-  (): Codec<TalerCorebankApi.Config> =>
-    buildCodecForObject<TalerCorebankApi.Config>()
-      .property("name", codecForConstString("libeufin-bank"))
-      .property("version", codecForString())
-      .property("allow_conversion", codecForBoolean())
-      .property("allow_deletions", codecForBoolean())
-      .property("allow_registrations", codecForBoolean())
-      .property("currency_specification", codecForCurrencySpecificiation())
       .property("currency", codecForString())
-      .build("TalerCorebankApi.Config")
+      .build("TalerCorebankApi.IntegrationConfig");
+
+export const codecForCoreBankConfig = (): Codec<TalerCorebankApi.Config> =>
+  buildCodecForObject<TalerCorebankApi.Config>()
+    .property("name", codecForConstString("libeufin-bank"))
+    .property("version", codecForString())
+    .property("allow_conversion", codecForBoolean())
+    .property("allow_deletions", codecForBoolean())
+    .property("allow_registrations", codecForBoolean())
+    .property("currency_specification", codecForCurrencySpecificiation())
+    .property("currency", codecForString())
+    .build("TalerCorebankApi.Config");
 
 export const codecForMerchantConfig =
   (): Codec<TalerMerchantApi.VersionResponse> =>
@@ -279,7 +284,7 @@ export const codecForMerchantConfig =
       .property("currency", codecForString())
       .property("version", codecForString())
       .property("currencies", codecForMap(codecForCurrencySpecificiation()))
-      .build("TalerMerchantApi.VersionResponse")
+      .build("TalerMerchantApi.VersionResponse");
 
 export const codecForExchangeConfig =
   (): Codec<TalerExchangeApi.ExchangeVersionResponse> =>
@@ -289,27 +294,32 @@ export const codecForExchangeConfig =
       .property("currency", codecForString())
       .property("currency_specification", codecForCurrencySpecificiation())
       .property("supported_kyc_requirements", codecForList(codecForString()))
-      .build("TalerExchangeApi.ExchangeVersionResponse")
+      .build("TalerExchangeApi.ExchangeVersionResponse");
 
 const codecForBalance = (): Codec<TalerCorebankApi.Balance> =>
   buildCodecForObject<TalerCorebankApi.Balance>()
     .property("amount", codecForAmountString())
-    .property("credit_debit_indicator", 
codecForEither(codecForConstString("credit"), codecForConstString("debit")))
-    .build("TalerCorebankApi.Balance")
+    .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", codecForPaytoString())
-    .build("TalerCorebankApi.PublicAccount")
+    .build("TalerCorebankApi.PublicAccount");
 
 export const codecForPublicAccountsResponse =
   (): Codec<TalerCorebankApi.PublicAccountsResponse> =>
     buildCodecForObject<TalerCorebankApi.PublicAccountsResponse>()
       .property("public_accounts", codecForList(codecForPublicAccount()))
-      .build("TalerCorebankApi.PublicAccountsResponse")
-
+      .build("TalerCorebankApi.PublicAccountsResponse");
 
 export const codecForAccountMinimalData =
   (): Codec<TalerCorebankApi.AccountMinimalData> =>
@@ -318,37 +328,38 @@ export const codecForAccountMinimalData =
       .property("debit_threshold", codecForAmountString())
       .property("name", codecForString())
       .property("username", codecForString())
-      .build("TalerCorebankApi.AccountMinimalData")
+      .build("TalerCorebankApi.AccountMinimalData");
 
 export const codecForListBankAccountsResponse =
   (): Codec<TalerCorebankApi.ListBankAccountsResponse> =>
     buildCodecForObject<TalerCorebankApi.ListBankAccountsResponse>()
       .property("accounts", codecForList(codecForAccountMinimalData()))
-      .build("TalerCorebankApi.ListBankAccountsResponse")
-
-export const codecForAccountData =
-  (): Codec<TalerCorebankApi.AccountData> =>
-    buildCodecForObject<TalerCorebankApi.AccountData>()
-      .property("name", codecForString())
-      .property("balance", codecForBalance())
-      .property("payto_uri", codecForPaytoString())
-      .property("debit_threshold", codecForAmountString())
-      .property("contact_data", codecOptional(codecForChallengeContactData()))
-      .property("cashout_payto_uri", codecOptional(codecForPaytoString()))
-      .build("TalerCorebankApi.AccountData")
+      .build("TalerCorebankApi.ListBankAccountsResponse");
 
+export const codecForAccountData = (): Codec<TalerCorebankApi.AccountData> =>
+  buildCodecForObject<TalerCorebankApi.AccountData>()
+    .property("name", codecForString())
+    .property("balance", codecForBalance())
+    .property("payto_uri", codecForPaytoString())
+    .property("debit_threshold", codecForAmountString())
+    .property("contact_data", codecOptional(codecForChallengeContactData()))
+    .property("cashout_payto_uri", codecOptional(codecForPaytoString()))
+    .build("TalerCorebankApi.AccountData");
 
 export const codecForChallengeContactData =
   (): Codec<TalerCorebankApi.ChallengeContactData> =>
     buildCodecForObject<TalerCorebankApi.ChallengeContactData>()
       .property("email", codecOptional(codecForString()))
       .property("phone", codecOptional(codecForString()))
-      .build("TalerCorebankApi.ChallengeContactData")
+      .build("TalerCorebankApi.ChallengeContactData");
 
 export const codecForBankAccountTransactionsResponse =
   (): Codec<TalerCorebankApi.BankAccountTransactionsResponse> =>
     buildCodecForObject<TalerCorebankApi.BankAccountTransactionsResponse>()
-      .property("transactions", 
codecForList(codecForBankAccountTransactionInfo()))
+      .property(
+        "transactions",
+        codecForList(codecForBankAccountTransactionInfo()),
+      )
       .build("TalerCorebankApi.BankAccountTransactionsResponse");
 
 export const codecForBankAccountTransactionInfo =
@@ -357,7 +368,13 @@ export const codecForBankAccountTransactionInfo =
       .property("creditor_payto_uri", codecForPaytoString())
       .property("debtor_payto_uri", codecForPaytoString())
       .property("amount", codecForAmountString())
-      .property("direction", codecForEither(codecForConstString("debit"), 
codecForConstString("credit")))
+      .property(
+        "direction",
+        codecForEither(
+          codecForConstString("debit"),
+          codecForConstString("credit"),
+        ),
+      )
       .property("subject", codecForString())
       .property("row_id", codecForNumber())
       .property("date", codecForTimestamp)
@@ -376,9 +393,12 @@ export const codecForBankAccountGetWithdrawalResponse =
       .property("amount", codecForAmountString())
       .property("aborted", codecForBoolean())
       .property("confirmation_done", codecForBoolean())
-      .property("selected_exchange_account", 
codecOptional(codecForPaytoString()))
+      .property(
+        "selected_exchange_account",
+        codecOptional(codecForPaytoString()),
+      )
       .property("selected_reserve_pub", codecOptional(codecForString()))
-      .property("selection_done", (codecForBoolean()))
+      .property("selection_done", codecForBoolean())
       .build("TalerCorebankApi.BankAccountGetWithdrawalResponse");
 
 export const codecForCashoutPending =
@@ -401,18 +421,23 @@ export const codecForCashinConversionResponse =
       .property("amount_debit", codecForAmountString())
       .build("TalerCorebankApi.CashinConversionResponse");
 
-export const codecForCashouts =
-  (): Codec<TalerCorebankApi.Cashouts> =>
-    buildCodecForObject<TalerCorebankApi.Cashouts>()
-      .property("cashouts", codecForList(codecForCashoutInfo()))
-      .build("TalerCorebankApi.Cashouts");
-
-export const codecForCashoutInfo =
-  (): Codec<TalerCorebankApi.CashoutInfo> =>
-    buildCodecForObject<TalerCorebankApi.CashoutInfo>()
-      .property("cashout_id", codecForString())
-      .property("status", codecForEither(codecForConstString("pending"), 
codecForConstString("aborted"), codecForConstString("confirmed"),))
-      .build("TalerCorebankApi.CashoutInfo");
+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("aborted"),
+        codecForConstString("confirmed"),
+      ),
+    )
+    .build("TalerCorebankApi.CashoutInfo");
 
 export const codecForGlobalCashouts =
   (): Codec<TalerCorebankApi.GlobalCashouts> =>
@@ -425,7 +450,14 @@ export const codecForGlobalCashoutInfo =
     buildCodecForObject<TalerCorebankApi.GlobalCashoutInfo>()
       .property("cashout_id", codecForString())
       .property("username", codecForString())
-      .property("status", codecForEither(codecForConstString("pending"), 
codecForConstString("aborted"), codecForConstString("confirmed"),))
+      .property(
+        "status",
+        codecForEither(
+          codecForConstString("pending"),
+          codecForConstString("aborted"),
+          codecForConstString("confirmed"),
+        ),
+      )
       .build("TalerCorebankApi.GlobalCashoutInfo");
 
 export const codecForCashoutStatusResponse =
@@ -436,7 +468,14 @@ export const codecForCashoutStatusResponse =
       .property("confirmation_time", codecForTimestamp)
       .property("creation_time", codecForTimestamp)
       // .property("credit_payto_uri", codecForPaytoString())
-      .property("status", codecForEither(codecForConstString("pending"), 
codecForConstString("aborted"), codecForConstString("confirmed")))
+      .property(
+        "status",
+        codecForEither(
+          codecForConstString("pending"),
+          codecForConstString("aborted"),
+          codecForConstString("confirmed"),
+        ),
+      )
       .property("subject", codecForString())
       .build("TalerCorebankApi.CashoutStatusResponse");
 
@@ -449,12 +488,13 @@ export const codecForConversionRatesResponse =
       .property("sell_out_fee", codecForDecimalNumber())
       .build("TalerCorebankApi.ConversionRatesResponse");
 
-
-export const codecForMonitorResponse = (): 
Codec<TalerCorebankApi.MonitorResponse> => 
buildCodecForUnion<TalerCorebankApi.MonitorResponse>()
-  .discriminateOn("type")
-  .alternative("no-conversions", codecForMonitorNoConversion())
-  .alternative("with-conversions", codecForMonitorWithCashout())
-  .build("TalerWireGatewayApi.IncomingBankTransaction");
+export const codecForMonitorResponse =
+  (): Codec<TalerCorebankApi.MonitorResponse> =>
+    buildCodecForUnion<TalerCorebankApi.MonitorResponse>()
+      .discriminateOn("type")
+      .alternative("no-conversions", codecForMonitorNoConversion())
+      .alternative("with-conversions", codecForMonitorWithCashout())
+      .build("TalerWireGatewayApi.IncomingBankTransaction");
 
 export const codecForMonitorNoConversion =
   (): Codec<TalerCorebankApi.MonitorNoConversion> =>
@@ -515,7 +555,10 @@ export const codecForMerchantIncomingHistory =
   (): Codec<TalerRevenueApi.MerchantIncomingHistory> =>
     buildCodecForObject<TalerRevenueApi.MerchantIncomingHistory>()
       .property("credit_account", codecForPaytoString())
-      .property("incoming_transactions", 
codecForList(codecForMerchantIncomingBankTransaction()))
+      .property(
+        "incoming_transactions",
+        codecForList(codecForMerchantIncomingBankTransaction()),
+      )
       .build("TalerRevenueApi.MerchantIncomingHistory");
 
 export const codecForMerchantIncomingBankTransaction =
@@ -540,14 +583,19 @@ export const codecForIncomingHistory =
   (): Codec<TalerWireGatewayApi.IncomingHistory> =>
     buildCodecForObject<TalerWireGatewayApi.IncomingHistory>()
       .property("credit_account", codecForPaytoString())
-      .property("incoming_transactions", 
codecForList(codecForIncomingBankTransaction()))
+      .property(
+        "incoming_transactions",
+        codecForList(codecForIncomingBankTransaction()),
+      )
       .build("TalerWireGatewayApi.IncomingHistory");
 
-export const codecForIncomingBankTransaction = (): 
Codec<TalerWireGatewayApi.IncomingBankTransaction> => 
buildCodecForUnion<TalerWireGatewayApi.IncomingBankTransaction>()
-  .discriminateOn("type")
-  .alternative("RESERVE", codecForIncomingReserveTransaction())
-  .alternative("WAD", codecForIncomingWadTransaction())
-  .build("TalerWireGatewayApi.IncomingBankTransaction");
+export const codecForIncomingBankTransaction =
+  (): Codec<TalerWireGatewayApi.IncomingBankTransaction> =>
+    buildCodecForUnion<TalerWireGatewayApi.IncomingBankTransaction>()
+      .discriminateOn("type")
+      .alternative("RESERVE", codecForIncomingReserveTransaction())
+      .alternative("WAD", codecForIncomingWadTransaction())
+      .build("TalerWireGatewayApi.IncomingBankTransaction");
 
 export const codecForIncomingReserveTransaction =
   (): Codec<TalerWireGatewayApi.IncomingReserveTransaction> =>
@@ -577,7 +625,10 @@ export const codecForOutgoingHistory =
   (): Codec<TalerWireGatewayApi.OutgoingHistory> =>
     buildCodecForObject<TalerWireGatewayApi.OutgoingHistory>()
       .property("debit_account", codecForPaytoString())
-      .property("outgoing_transactions", 
codecForList(codecForOutgoingBankTransaction()))
+      .property(
+        "outgoing_transactions",
+        codecForList(codecForOutgoingBankTransaction()),
+      )
       .build("TalerWireGatewayApi.OutgoingHistory");
 
 export const codecForOutgoingBankTransaction =
@@ -598,20 +649,18 @@ export const codecForAddIncomingResponse =
       .property("timestamp", codecForTimestamp)
       .build("TalerWireGatewayApi.AddIncomingResponse");
 
-export const codecForAmlRecords =
-  (): Codec<TalerExchangeApi.AmlRecords> =>
-    buildCodecForObject<TalerExchangeApi.AmlRecords>()
-      .property("records", codecForList(codecForAmlRecord()))
-      .build("TalerExchangeApi.PublicAccountsResponse");
-
-export const codecForAmlRecord =
-  (): Codec<TalerExchangeApi.AmlRecord> =>
-    buildCodecForObject<TalerExchangeApi.AmlRecord>()
-      .property("current_state", codecForNumber())
-      .property("h_payto", codecForString())
-      .property("rowid", codecForNumber())
-      .property("threshold", codecForAmountString())
-      .build("TalerExchangeApi.AmlRecord");
+export const codecForAmlRecords = (): Codec<TalerExchangeApi.AmlRecords> =>
+  buildCodecForObject<TalerExchangeApi.AmlRecords>()
+    .property("records", codecForList(codecForAmlRecord()))
+    .build("TalerExchangeApi.PublicAccountsResponse");
+
+export const codecForAmlRecord = (): Codec<TalerExchangeApi.AmlRecord> =>
+  buildCodecForObject<TalerExchangeApi.AmlRecord>()
+    .property("current_state", codecForNumber())
+    .property("h_payto", codecForString())
+    .property("rowid", codecForNumber())
+    .property("threshold", codecForAmountString())
+    .build("TalerExchangeApi.AmlRecord");
 
 export const codecForAmlDecisionDetails =
   (): Codec<TalerExchangeApi.AmlDecisionDetails> =>
@@ -636,27 +685,24 @@ interface KycDetail {
   collection_time: Timestamp;
   expiration_time: Timestamp;
 }
-export const codecForKycDetail =
-  (): Codec<TalerExchangeApi.KycDetail> =>
-    buildCodecForObject<TalerExchangeApi.KycDetail>()
-      .property("provider_section", codecForString())
-      .property("attributes", codecOptional(codecForAny()))
-      .property("collection_time", codecForTimestamp)
-      .property("expiration_time", codecForTimestamp)
-      .build("TalerExchangeApi.KycDetail");
-
-export const codecForAmlDecision =
-  (): Codec<TalerExchangeApi.AmlDecision> =>
-    buildCodecForObject<TalerExchangeApi.AmlDecision>()
-      .property("justification", codecForString())
-      .property("new_threshold", codecForAmountString())
-      .property("h_payto", codecForString())
-      .property("new_state", codecForNumber())
-      .property("officer_sig", codecForString())
-      .property("decision_time", codecForTimestamp)
-      .property("kyc_requirements", 
codecOptional(codecForList(codecForString())))
-      .build("TalerExchangeApi.AmlDecision");
-
+export const codecForKycDetail = (): Codec<TalerExchangeApi.KycDetail> =>
+  buildCodecForObject<TalerExchangeApi.KycDetail>()
+    .property("provider_section", codecForString())
+    .property("attributes", codecOptional(codecForAny()))
+    .property("collection_time", codecForTimestamp)
+    .property("expiration_time", codecForTimestamp)
+    .build("TalerExchangeApi.KycDetail");
+
+export const codecForAmlDecision = (): Codec<TalerExchangeApi.AmlDecision> =>
+  buildCodecForObject<TalerExchangeApi.AmlDecision>()
+    .property("justification", codecForString())
+    .property("new_threshold", codecForAmountString())
+    .property("h_payto", codecForString())
+    .property("new_state", codecForNumber())
+    .property("officer_sig", codecForString())
+    .property("decision_time", codecForTimestamp)
+    .property("kyc_requirements", 
codecOptional(codecForList(codecForString())))
+    .build("TalerExchangeApi.AmlDecision");
 
 // version: string;
 
@@ -710,23 +756,29 @@ export const codecForConversionInfo =
       .property("cashin_min_amount", codecForAmountString())
       .property("cashin_ratio", codecForString())
       // .property("cashin_ratio", codecForDecimalNumber())
-      .property("cashin_rounding_mode", codecForEither(
-        codecForConstString("zero"),
-        codecForConstString("up"),
-        codecForConstString("nearest")
-      ))
+      .property(
+        "cashin_rounding_mode",
+        codecForEither(
+          codecForConstString("zero"),
+          codecForConstString("up"),
+          codecForConstString("nearest"),
+        ),
+      )
       .property("cashin_tiny_amount", codecForAmountString())
       .property("cashout_fee", codecForAmountString())
       .property("cashout_min_amount", codecForAmountString())
       .property("cashout_ratio", codecForString())
       // .property("cashout_ratio", codecForDecimalNumber())
-      .property("cashout_rounding_mode", codecForEither(
-        codecForConstString("zero"),
-        codecForConstString("up"),
-        codecForConstString("nearest")
-      ))
+      .property(
+        "cashout_rounding_mode",
+        codecForEither(
+          codecForConstString("zero"),
+          codecForConstString("up"),
+          codecForConstString("nearest"),
+        ),
+      )
       .property("cashout_tiny_amount", codecForAmountString())
-      .build("ConversionBankConfig.ConversionInfo")
+      .build("ConversionBankConfig.ConversionInfo");
 
 export const codecForConversionBankConfig =
   (): Codec<TalerBankConversionApi.IntegrationConfig> =>
@@ -734,7 +786,10 @@ export const codecForConversionBankConfig =
       .property("name", codecForConstString("taler-conversion-info"))
       .property("version", codecForString())
       .property("regional_currency", codecForString())
-      .property("regional_currency_specification", 
codecForCurrencySpecificiation())
+      .property(
+        "regional_currency_specification",
+        codecForCurrencySpecificiation(),
+      )
       .property("fiat_currency", codecForString())
       .property("fiat_currency_specification", 
codecForCurrencySpecificiation())
       // .property("conversion_info", codecOptional(codecForConversionInfo()))
@@ -742,30 +797,35 @@ export const codecForConversionBankConfig =
       .property("cashin_fee", codecForAmountString())
       .property("cashin_min_amount", codecForAmountString())
       .property("cashin_ratio", codecForString())
-      .property("cashin_rounding_mode", codecForEither(
-        codecForConstString("zero"),
-        codecForConstString("up"),
-        codecForConstString("nearest")
-      ))
+      .property(
+        "cashin_rounding_mode",
+        codecForEither(
+          codecForConstString("zero"),
+          codecForConstString("up"),
+          codecForConstString("nearest"),
+        ),
+      )
       .property("cashin_tiny_amount", codecForAmountString())
       .property("cashout_fee", codecForAmountString())
       .property("cashout_min_amount", codecForAmountString())
       .property("cashout_ratio", codecForString())
-      .property("cashout_rounding_mode", codecForEither(
-        codecForConstString("zero"),
-        codecForConstString("up"),
-        codecForConstString("nearest")
-      ))
+      .property(
+        "cashout_rounding_mode",
+        codecForEither(
+          codecForConstString("zero"),
+          codecForConstString("up"),
+          codecForConstString("nearest"),
+        ),
+      )
       .property("cashout_tiny_amount", codecForAmountString())
       //////////////////////////
-      .build("ConversionBankConfig.IntegrationConfig")
+      .build("ConversionBankConfig.IntegrationConfig");
 // export const codecFor =
 //   (): Codec<TalerWireGatewayApi.PublicAccountsResponse> =>
 //     buildCodecForObject<TalerWireGatewayApi.PublicAccountsResponse>()
 //       .property("", codecForString())
 //       .build("TalerWireGatewayApi.PublicAccountsResponse");
 
-
 type EmailAddress = string;
 type PhoneNumber = string;
 type EddsaSignature = string;
@@ -782,7 +842,7 @@ type CoinEnvelope = RSACoinEnvelope | CSCoinEnvelope;
 // coin's public EdDSA key.
 interface RSACoinEnvelope {
   cipher: "RSA" | "RSA+age_restricted";
-  rsa_blinded_planchet: string;          // Crockford Base32 encoded
+  rsa_blinded_planchet: string; // Crockford Base32 encoded
 }
 // For denomination signatures based on Blind Clause-Schnorr, the planchet
 // consists of the public nonce and two Curve25519 scalars which are two
@@ -790,7 +850,7 @@ interface RSACoinEnvelope {
 // See https://taler.net/papers/cs-thesis.pdf for details.
 interface CSCoinEnvelope {
   cipher: "CS" | "CS+age_restricted";
-  cs_nonce: string;      // Crockford Base32 encoded
+  cs_nonce: string; // Crockford Base32 encoded
   cs_blinded_c0: string; // Crockford Base32 encoded
   cs_blinded_c1: string; // Crockford Base32 encoded
 }
@@ -799,11 +859,11 @@ interface CSCoinEnvelope {
 // a 256-bit nonce, converted to Crockford Base32.
 type DenominationBlindingKeyP = string;
 
-const codecForURL = codecForString
-const codecForLibtoolVersion = codecForString
-const codecForCurrencyName = codecForString
-const codecForEddsaSignature = codecForString
-const codecForDecimalNumber = codecForNumber
+const codecForURL = codecForString;
+const codecForLibtoolVersion = codecForString;
+const codecForCurrencyName = codecForString;
+const codecForEddsaSignature = codecForString;
+const codecForDecimalNumber = codecForNumber;
 
 enum TanChannel {
   SMS = "sms",
@@ -811,9 +871,7 @@ enum TanChannel {
 }
 
 export namespace TalerWireGatewayApi {
-
   export interface TransferResponse {
-
     // Timestamp that indicates when the wire transfer will be executed.
     // In cases where the wire transfer gateway is unable to know when
     // the wire transfer will be executed, the time at which the request
@@ -850,7 +908,6 @@ export namespace TalerWireGatewayApi {
   }
 
   export interface IncomingHistory {
-
     // Array of incoming transactions.
     incoming_transactions: IncomingBankTransaction[];
 
@@ -859,7 +916,6 @@ export namespace TalerWireGatewayApi {
     // Credit account is shared by all incoming transactions
     // as per the nature of the request.
     credit_account: PaytoString;
-
   }
 
   // Union discriminated by the "type" field.
@@ -884,7 +940,6 @@ export namespace TalerWireGatewayApi {
 
     // The reserve public key extracted from the transaction details.
     reserve_pub: EddsaPublicKey;
-
   }
 
   export interface IncomingWadTransaction {
@@ -913,9 +968,7 @@ export namespace TalerWireGatewayApi {
     wad_id: WadId;
   }
 
-
   export interface OutgoingHistory {
-
     // Array of outgoing transactions.
     outgoing_transactions: OutgoingBankTransaction[];
 
@@ -924,11 +977,9 @@ export namespace TalerWireGatewayApi {
     // Credit account is shared by all incoming transactions
     // as per the nature of the request.
     debit_account: PaytoString;
-
   }
 
   export interface OutgoingBankTransaction {
-
     // Opaque identifier of the returned record.
     row_id: SafeUint64;
 
@@ -964,7 +1015,6 @@ export namespace TalerWireGatewayApi {
   }
 
   export interface AddIncomingResponse {
-
     // Timestamp that indicates when the wire transfer will be executed.
     // In cases where the wire transfer gateway is unable to know when
     // the wire transfer will be executed, the time at which the request
@@ -977,14 +1027,10 @@ export namespace TalerWireGatewayApi {
     // Opaque ID of the transaction that the bank has made.
     row_id: SafeUint64;
   }
-
-
-
 }
 
 export namespace TalerRevenueApi {
   export interface MerchantIncomingHistory {
-
     // Array of incoming transactions.
     incoming_transactions: MerchantIncomingBankTransaction[];
 
@@ -993,11 +1039,9 @@ export namespace TalerRevenueApi {
     // Credit account is shared by all incoming transactions
     // as per the nature of the request.
     credit_account: PaytoString;
-
   }
 
   export interface MerchantIncomingBankTransaction {
-
     // Opaque identifier of the returned record.
     row_id: SafeUint64;
 
@@ -1019,7 +1063,6 @@ export namespace TalerRevenueApi {
 }
 
 export namespace TalerBankConversionApi {
-
   export interface ConversionInfo {
     // Exchange rate to buy regional currency from fiat
     // cashin_ratio: DecimalNumber;
@@ -1097,7 +1140,6 @@ export namespace TalerBankConversionApi {
     // bank account, according to 'amount_debit'.
     amount_credit: AmountString;
   }
-
 }
 export namespace TalerBankIntegrationApi {
   export interface BankVersion {
@@ -1110,7 +1152,7 @@ export namespace TalerBankIntegrationApi {
     currency: string;
 
     // How the bank SPA should render this currency.
-    currency_specification: CurrencySpecification;
+    currency_specification?: CurrencySpecification;
 
     // Name of the API.
     name: "taler-bank-integration";
@@ -1150,7 +1192,6 @@ export namespace TalerBankIntegrationApi {
   }
 
   export interface BankWithdrawalOperationPostRequest {
-
     // Reserve public key.
     reserve_pub: string;
 
@@ -1159,7 +1200,6 @@ export namespace TalerBankIntegrationApi {
   }
 
   export interface BankWithdrawalOperationPostResponse {
-
     // The transfer has been confirmed and registered by the bank.
     // Does not guarantee that the funds have arrived at the exchange already.
     transfer_done: boolean;
@@ -1171,23 +1211,18 @@ export namespace TalerBankIntegrationApi {
     // It may contain withdrawal operation id
     confirm_transfer_url?: string;
   }
-
-
 }
 export namespace TalerCorebankApi {
-
   export interface IntegrationConfig {
     // libtool-style representation of the Bank protocol version, see
     // 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
     // The format is "current:revision:age".
     version: string;
 
-    // How the bank SPA should render this currency.
-    currency: CurrencySpecification;
+    currency: String;
 
     // Name of the API.
     name: "taler-bank-integration";
-
   }
   export interface Config {
     // Name of this API, always "taler-corebank".
@@ -1323,7 +1358,6 @@ export namespace TalerCorebankApi {
     internal_payto_uri?: PaytoString;
   }
   export interface ChallengeContactData {
-
     // E-Mail address
     email?: EmailAddress;
 
@@ -1332,7 +1366,6 @@ export namespace TalerCorebankApi {
   }
 
   export interface AccountReconfiguration {
-
     // Addresses where to send the TAN for transactions.
     // Currently only used for cashouts.
     // If missing, cashouts will fail.
@@ -1357,11 +1390,9 @@ export namespace TalerCorebankApi {
 
     // If present, change the max debit allowed for this user
     // Only admin can change this property.
-    debit_threshold?: AmountString
-
+    debit_threshold?: AmountString;
   }
 
-
   export interface AccountPasswordChange {
     // New password.
     new_password: string;
@@ -1428,9 +1459,7 @@ export namespace TalerCorebankApi {
     cashout_payto_uri?: PaytoString;
   }
 
-
   export interface CashoutRequest {
-
     // Nonce to make the request idempotent.  Requests with the same
     // request_uid that differ in any of the other fields
     // are rejected.
@@ -1521,7 +1550,6 @@ export namespace TalerCorebankApi {
   }
 
   export interface ConversionRatesResponse {
-
     // Exchange rate to buy the local currency from the external one
     buy_at_ratio: DecimalNumber;
 
@@ -1536,12 +1564,14 @@ export namespace TalerCorebankApi {
   }
 
   export enum MonitorTimeframeParam {
-    hour, day, month, year, decade,
+    hour,
+    day,
+    month,
+    year,
+    decade,
   }
 
-  export type MonitorResponse =
-    | MonitorNoConversion
-    | MonitorWithConversion;
+  export type MonitorResponse = MonitorNoConversion | MonitorWithConversion;
 
   // Monitoring stats when conversion is not supported
   export interface MonitorNoConversion {
@@ -1607,12 +1637,9 @@ export namespace TalerCorebankApi {
     // exchange to another bank account.
     talerOutVolume: AmountString;
   }
-
-
 }
 
 export namespace TalerExchangeApi {
-
   export enum AmlState {
     normal = 0,
     pending = 1,
@@ -1620,12 +1647,10 @@ export namespace TalerExchangeApi {
   }
 
   export interface AmlRecords {
-
     // Array of AML records matching the query.
     records: AmlRecord[];
   }
   export interface AmlRecord {
-
     // Which payto-address is this record about.
     // Identifies a GNU Taler wallet or an affected bank account.
     h_payto: PaytoHash;
@@ -1638,11 +1663,9 @@ export namespace TalerExchangeApi {
 
     // RowID of the record.
     rowid: Integer;
-
   }
 
   export interface AmlDecisionDetails {
-
     // Array of AML decisions made for this account. Possibly
     // contains only the most recent decision if "history" was
     // not set to 'true'.
@@ -1652,7 +1675,6 @@ export namespace TalerExchangeApi {
     kyc_attributes: KycDetail[];
   }
   export interface AmlDecisionDetail {
-
     // What was the justification given?
     justification: string;
 
@@ -1667,10 +1689,8 @@ export namespace TalerExchangeApi {
 
     // Who made the decision?
     decider_pub: AmlOfficerPublicKeyP;
-
   }
   export interface KycDetail {
-
     // Name of the configuration section that specifies the provider
     // which was used to collect the KYC details
     provider_section: string;
@@ -1685,12 +1705,9 @@ export namespace TalerExchangeApi {
 
     // Time when the validity of the KYC data will expire
     expiration_time: Timestamp;
-
   }
 
-
   export interface AmlDecision {
-
     // Human-readable justification for the decision.
     justification: string;
 
@@ -1719,7 +1736,6 @@ export namespace TalerExchangeApi {
     kyc_requirements?: string[];
   }
 
-
   export interface ExchangeVersionResponse {
     // libtool-style representation of the Exchange protocol version, see
     // 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
@@ -1737,23 +1753,20 @@ export namespace TalerExchangeApi {
 
     // Names of supported KYC requirements.
     supported_kyc_requirements: string[];
-
   }
 
   export type AccountRestriction =
     | RegexAccountRestriction
-    | DenyAllAccountRestriction
+    | DenyAllAccountRestriction;
   // Account restriction that disables this type of
   // account for the indicated operation categorically.
   export interface DenyAllAccountRestriction {
-
     type: "deny";
   }
   // Accounts interacting with this type of account
   // restriction must have a payto://-URI matching
   // the given regex.
   export interface RegexAccountRestriction {
-
     type: "regex";
 
     // Regular expression that the payto://-URI of the
@@ -1773,7 +1786,6 @@ export namespace TalerExchangeApi {
     // Map from IETF BCP 47 language tags to localized
     // human hints.
     human_hint_i18n?: { [lang_tag: string]: string };
-
   }
 
   export interface WireAccount {
@@ -1800,7 +1812,6 @@ export namespace TalerExchangeApi {
     // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS.
     master_sig: EddsaSignature;
   }
-
 }
 
 export namespace TalerMerchantApi {
@@ -1827,7 +1838,6 @@ export namespace TalerMerchantApi {
     // All currencies in this map are supported by
     // the backend.
     currencies: { [currency: string]: CurrencySpecification };
-
   }
 
   export interface ClaimRequest {
@@ -1856,7 +1866,6 @@ export namespace TalerMerchantApi {
     // Text to be shown to the point-of-sale staff as a proof of
     // payment.
     pos_confirmation?: string;
-
   }
 
   interface PayRequest {
@@ -1869,7 +1878,6 @@ export namespace TalerMerchantApi {
     // The session for which the payment is made (or replayed).
     // Only set for session-based payments.
     session_id?: string;
-
   }
   export interface CoinPaySig {
     // Signature by the coin.
@@ -1891,7 +1899,6 @@ export namespace TalerMerchantApi {
     exchange_url: string;
   }
 
-
   interface StatusPaid {
     // Was the payment refunded (even partially, via refund or abort)?
     refunded: boolean;
@@ -1926,7 +1933,6 @@ export namespace TalerMerchantApi {
   }
 
   interface PaidRefundStatusResponse {
-
     // Text to be shown to the point-of-sale staff as a proof of
     // payment (present only if re-usable OTP algorithm is used).
     pos_confirmation?: string;
@@ -1953,7 +1959,6 @@ export namespace TalerMerchantApi {
   }
 
   interface AbortRequest {
-
     // Hash of the order's contract terms (this is used to authenticate the
     // wallet/customer in case $ORDER_ID is guessable).
     h_contract: HashCode;
@@ -1974,7 +1979,6 @@ export namespace TalerMerchantApi {
     exchange_url: string;
   }
   interface AbortResponse {
-
     // List of refund responses about the coins that the wallet
     // requested an abort for.  In the same order as the coins
     // from the original request.
@@ -2034,7 +2038,6 @@ export namespace TalerMerchantApi {
 
     // Public key of the merchant.
     merchant_pub: EddsaPublicKey;
-
   }
   type MerchantCoinRefundStatus =
     | MerchantCoinRefundSuccessStatus
@@ -2104,7 +2107,6 @@ export namespace TalerMerchantApi {
   }
 
   interface RewardInformation {
-
     // Exchange from which the reward will be withdrawn. Needed by the
     // wallet to determine denominations, fees, etc.
     exchange_url: string;
@@ -2121,7 +2123,6 @@ export namespace TalerMerchantApi {
   }
 
   interface RewardPickupRequest {
-
     // List of planchets the wallet wants to use for the reward.
     planchets: PlanchetDetail[];
   }
@@ -2134,19 +2135,16 @@ export namespace TalerMerchantApi {
     coin_ev: CoinEnvelope;
   }
   interface RewardResponse {
-
     // Blind RSA signatures over the planchets.
     // The order of the signatures matches the planchets list.
     blind_sigs: BlindSignature[];
   }
   interface BlindSignature {
-
     // The (blind) RSA signature. Still needs to be unblinded.
     blind_sig: BlindedRsaSignature;
   }
 
   interface InstanceConfigurationMessage {
-
     // Name of the merchant instance to create (will become $INSTANCE).
     // Must match the regex ^[A-Za-z0-9][A-Za-z0-9_.@-]+$.
     id: string;
@@ -2192,7 +2190,6 @@ export namespace TalerMerchantApi {
     // If the frontend does NOT specify a payment deadline, how long should
     // offers we make be valid by default?
     default_pay_delay: RelativeTime;
-
   }
 
   interface InstanceAuthConfigurationMessage {
@@ -2210,7 +2207,6 @@ export namespace TalerMerchantApi {
     // the value must be provided in a "Authorization: Bearer $token"
     // header.
     token?: string;
-
   }
 
   interface LoginTokenRequest {
@@ -2244,7 +2240,6 @@ export namespace TalerMerchantApi {
   }
 
   interface InstanceReconfigurationMessage {
-
     // Merchant name corresponding to this instance.
     name: string;
 
@@ -2283,7 +2278,6 @@ export namespace TalerMerchantApi {
     // If the frontend does NOT specify a payment deadline, how long should
     // offers we make be valid by default?
     default_pay_delay: RelativeTime;
-
   }
 
   interface InstancesResponse {
@@ -2320,7 +2314,6 @@ export namespace TalerMerchantApi {
   }
 
   interface QueryInstancesResponse {
-
     // Merchant name corresponding to this instance.
     name: string;
 
@@ -2366,11 +2359,9 @@ export namespace TalerMerchantApi {
     auth: {
       type: "external" | "token";
     };
-
   }
 
   interface AccountKycRedirects {
-
     // Array of pending KYCs.
     pending_kycs: MerchantAccountKycRedirect[];
 
@@ -2379,7 +2370,6 @@ export namespace TalerMerchantApi {
   }
 
   interface MerchantAccountKycRedirect {
-
     // URL that the user should open in a browser to
     // proceed with the KYC process (as returned
     // by the exchange's /kyc-check/ endpoint).
@@ -2395,11 +2385,9 @@ export namespace TalerMerchantApi {
 
     // Our bank wire account this is about.
     payto_uri: PaytoString;
-
   }
 
   interface ExchangeKycTimeout {
-
     // Base URL of the exchange this is about.
     exchange_url: string;
 
@@ -2411,11 +2399,9 @@ export namespace TalerMerchantApi {
     // information about the KYC status.
     // 0 if there was no response at all.
     exchange_http_status: number;
-
   }
 
   interface AccountAddDetails {
-
     // payto:// URI of the account.
     payto_uri: PaytoString;
 
@@ -2429,15 +2415,12 @@ export namespace TalerMerchantApi {
     // or PATCH requests to update (or delete) credentials.
     // To really delete credentials, set them to the type: "none".
     credit_facade_credentials?: FacadeCredentials;
-
   }
 
-  type FacadeCredentials =
-    | NoFacadeCredentials
-    | BasicAuthFacadeCredentials;
+  type FacadeCredentials = NoFacadeCredentials | BasicAuthFacadeCredentials;
   interface NoFacadeCredentials {
     type: "none";
-  };
+  }
   interface BasicAuthFacadeCredentials {
     type: "basic";
 
@@ -2446,19 +2429,16 @@ export namespace TalerMerchantApi {
 
     // Password to use to authenticate
     password: string;
-  };
+  }
   interface AccountAddResponse {
-
     // Hash over the wire details (including over the salt).
     h_wire: HashCode;
 
     // Salt used to compute h_wire.
     salt: HashCode;
-
   }
 
   interface AccountPatchDetails {
-
     // URL from where the merchant can download information
     // about incoming wire transfers to this account.
     credit_facade_url?: string;
@@ -2474,12 +2454,10 @@ export namespace TalerMerchantApi {
   }
 
   interface AccountsSummaryResponse {
-
     // List of accounts that are known for the instance.
     accounts: BankAccountEntry[];
   }
   interface BankAccountEntry {
-
     // payto:// URI of the account.
     payto_uri: PaytoString;
 
@@ -2499,7 +2477,6 @@ export namespace TalerMerchantApi {
   }
 
   interface ProductAddDetail {
-
     // Product ID to use.
     product_id: string;
 
@@ -2539,11 +2516,9 @@ export namespace TalerMerchantApi {
 
     // Minimum age buyer must have (in years). Default is 0.
     minimum_age?: Integer;
-
   }
 
   interface ProductPatchDetail {
-
     // Human-readable product description.
     description: string;
 
@@ -2583,7 +2558,6 @@ export namespace TalerMerchantApi {
 
     // Minimum age buyer must have (in years). Default is 0.
     minimum_age?: Integer;
-
   }
 
   interface InventorySummaryResponse {
@@ -2591,15 +2565,12 @@ export namespace TalerMerchantApi {
     products: InventoryEntry[];
   }
 
-
   interface InventoryEntry {
     // Product identifier, as found in the product.
     product_id: string;
-
   }
 
   interface ProductDetail {
-
     // Human-readable product description.
     description: string;
 
@@ -2642,10 +2613,8 @@ export namespace TalerMerchantApi {
 
     // Minimum age buyer must have (in years).
     minimum_age?: Integer;
-
   }
   interface LockRequest {
-
     // UUID that identifies the frontend performing the lock
     // Must be unique for the lifetime of the lock.
     lock_uuid: string;
@@ -2655,7 +2624,6 @@ export namespace TalerMerchantApi {
 
     // How many units should be locked?
     quantity: Integer;
-
   }
 
   interface PostOrderRequest {
@@ -2735,7 +2703,6 @@ export namespace TalerMerchantApi {
     token?: ClaimToken;
   }
   interface OutOfStockResponse {
-
     // Product ID of an out-of-stock item.
     product_id: string;
 
@@ -2756,7 +2723,6 @@ export namespace TalerMerchantApi {
     orders: OrderHistoryEntry[];
   }
   interface OrderHistoryEntry {
-
     // Order ID of the transaction related to this entry.
     order_id: string;
 
@@ -2782,9 +2748,10 @@ export namespace TalerMerchantApi {
     paid: boolean;
   }
 
-  type MerchantOrderStatusResponse = CheckPaymentPaidResponse |
-    CheckPaymentClaimedResponse |
-    CheckPaymentUnpaidResponse;
+  type MerchantOrderStatusResponse =
+    | CheckPaymentPaidResponse
+    | CheckPaymentClaimedResponse
+    | CheckPaymentUnpaidResponse;
   interface CheckPaymentPaidResponse {
     // The customer paid for this contract.
     order_status: "paid";
@@ -2842,7 +2809,6 @@ export namespace TalerMerchantApi {
 
     // Contract terms.
     contract_terms: ContractTerms;
-
   }
   interface CheckPaymentUnpaidResponse {
     // The order was neither claimed nor paid.
@@ -2924,7 +2890,6 @@ export namespace TalerMerchantApi {
   }
 
   interface ForgetRequest {
-
     // Array of valid JSON paths to forgettable fields in the order's
     // contract terms.
     fields: string[];
@@ -2938,7 +2903,6 @@ export namespace TalerMerchantApi {
     reason: string;
   }
   interface MerchantRefundResponse {
-
     // URL (handled by the backend) that the wallet should access to
     // trigger refund processing.
     // taler://refund/...
@@ -3085,7 +3049,6 @@ export namespace TalerMerchantApi {
     exchange_url: string;
   }
   interface RewardStatusEntry {
-
     // Unique identifier for the reward.
     reward_id: HashCode;
 
@@ -3154,12 +3117,10 @@ export namespace TalerMerchantApi {
   }
 
   interface RewardsResponse {
-
     // List of rewards that are present in the backend.
     rewards: Reward[];
   }
   interface Reward {
-
     // ID of the reward in the backend database.
     row_id: number;
 
@@ -3171,7 +3132,6 @@ export namespace TalerMerchantApi {
   }
 
   interface OtpDeviceAddDetails {
-
     // Device ID to use.
     otp_device_id: string;
 
@@ -3189,7 +3149,6 @@ export namespace TalerMerchantApi {
   }
 
   interface OtpDevicePatchDetails {
-
     // Human-readable description for the device.
     otp_device_description: string;
 
@@ -3204,12 +3163,10 @@ export namespace TalerMerchantApi {
   }
 
   interface OtpDeviceSummaryResponse {
-
     // Array of devices that are present in our backend.
     otp_devices: OtpDeviceEntry[];
   }
   interface OtpDeviceEntry {
-
     // Device identifier.
     otp_device_id: string;
 
@@ -3218,7 +3175,6 @@ export namespace TalerMerchantApi {
   }
 
   interface OtpDeviceDetails {
-
     // Human-readable description for the device.
     device_description: string;
 
@@ -3227,10 +3183,8 @@ export namespace TalerMerchantApi {
 
     // Counter for counter-based OTP devices.
     otp_ctr?: Integer;
-
   }
   interface TemplateAddDetails {
-
     // Template ID to use.
     template_id: string;
 
@@ -3245,7 +3199,6 @@ export namespace TalerMerchantApi {
     template_contract: TemplateContractDetails;
   }
   interface TemplateContractDetails {
-
     // Human-readable summary for the template.
     summary?: string;
 
@@ -3266,10 +3219,8 @@ export namespace TalerMerchantApi {
     // The time the customer need to pay before his order will be deleted.
     // It is deleted if the customer did not pay and if the duration is over.
     pay_duration: RelativeTime;
-
   }
   interface TemplatePatchDetails {
-
     // Human-readable description for the template.
     template_description: string;
 
@@ -3279,27 +3230,21 @@ export namespace TalerMerchantApi {
 
     // Additional information in a separate template.
     template_contract: TemplateContractDetails;
-
   }
 
   interface TemplateSummaryResponse {
-
     // List of templates that are present in our backend.
     templates_list: TemplateEntry[];
   }
 
-
   interface TemplateEntry {
-
     // Template identifier, as found in the template.
     template_id: string;
 
     // Human-readable description for the template.
     template_description: string;
-
   }
   interface TemplateDetails {
-
     // Human-readable description for the template.
     template_description: string;
 
@@ -3311,7 +3256,6 @@ export namespace TalerMerchantApi {
     template_contract: TemplateContractDetails;
   }
   interface UsingTemplateDetails {
-
     // Summary of the template
     summary?: string;
 
@@ -3320,7 +3264,6 @@ export namespace TalerMerchantApi {
   }
 
   interface WebhookAddDetails {
-
     // Webhook ID to use.
     webhook_id: string;
 
@@ -3338,11 +3281,9 @@ export namespace TalerMerchantApi {
 
     // Body template by the webhook
     body_template?: string;
-
   }
 
   interface WebhookPatchDetails {
-
     // The event of the webhook: why the webhook is used.
     event_type: string;
 
@@ -3357,29 +3298,22 @@ export namespace TalerMerchantApi {
 
     // Body template by the webhook
     body_template?: string;
-
   }
 
   interface WebhookSummaryResponse {
-
     // Return webhooks that are present in our backend.
     webhooks: WebhookEntry[];
-
   }
 
-
   interface WebhookEntry {
-
     // Webhook identifier, as found in the webhook.
     webhook_id: string;
 
     // The event of the webhook: why the webhook is used.
     event_type: string;
-
   }
 
   interface WebhookDetails {
-
     // The event of the webhook: why the webhook is used.
     event_type: string;
 
@@ -3394,7 +3328,6 @@ export namespace TalerMerchantApi {
 
     // Body template by the webhook
     body_template?: string;
-
   }
 
   interface ContractTerms {
@@ -3639,5 +3572,4 @@ export namespace TalerMerchantApi {
     // Master public key of the exchange.
     master_pub: EddsaPublicKey;
   }
-
-}
\ No newline at end of file
+}
diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index f21efc516..e32c5a99d 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -41,6 +41,10 @@ import {
   codecOptional,
 } from "./codec.js";
 import { strcmp } from "./helpers.js";
+import {
+  CurrencySpecification,
+  codecForCurrencySpecificiation,
+} from "./index.js";
 import { AgeCommitmentProof, Edx25519PublicKeyEnc } from "./taler-crypto.js";
 import {
   codecForAbsoluteTime,
@@ -1756,7 +1760,6 @@ export interface MerchantAbortPayRefundSuccessStatus {
   exchange_pub: string;
 }
 
-
 export interface FutureKeysResponse {
   future_denoms: any[];
 
@@ -2375,3 +2378,39 @@ export const codecForExchangeWireAccount = (): 
Codec<ExchangeWireAccount> =>
     .property("master_sig", codecForString())
     .property("payto_uri", codecForString())
     .build("WireAccount");
+
+export type Integer = number;
+
+export interface BankConversionInfoConfig {
+  // libtool-style representation of the Bank protocol version, see
+  // 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+  // The format is "current:revision:age".
+  version: string;
+
+  // Name of the API.
+  name: "taler-conversion-info";
+
+  regional_currency: string;
+
+  fiat_currency: string;
+
+  // Currency used by this bank.
+  regional_currency_specification: CurrencySpecification;
+
+  // External currency used during conversion.
+  fiat_currency_specification: CurrencySpecification;
+}
+
+export const codecForBankConversionInfoConfig =
+  (): Codec<BankConversionInfoConfig> =>
+    buildCodecForObject<BankConversionInfoConfig>()
+      .property("name", codecForConstString("taler-conversion-info"))
+      .property("version", codecForString())
+      .property("fiat_currency", codecForString())
+      .property("regional_currency", codecForString())
+      .property("fiat_currency_specification", 
codecForCurrencySpecificiation())
+      .property(
+        "regional_currency_specification",
+        codecForCurrencySpecificiation(),
+      )
+      .build("BankConversionInfoConfig");
diff --git a/packages/taler-util/src/transactions-types.ts 
b/packages/taler-util/src/transactions-types.ts
index 3a7f4d541..f2bf49b00 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -51,6 +51,7 @@ import {
   TalerErrorDetail,
   TransactionIdStr,
   TransactionStateFilter,
+  WithdrawalAccountInfo,
 } from "./wallet-types.js";
 
 export interface TransactionsRequest {
@@ -237,9 +238,13 @@ interface WithdrawalDetailsForManualTransfer {
    * Payto URIs that the exchange supports.
    *
    * Already contains the amount and message.
+   *
+   * @deprecated in favor of exchangeCreditAccounts
    */
   exchangePaytoUris: string[];
 
+  exchangeCreditAccounts?: WithdrawalAccountInfo[];
+
   // Public key of the reserve
   reservePub: string;
 
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 148117673..346528029 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -52,6 +52,7 @@ import { PaytoUri } from "./payto.js";
 import { AgeCommitmentProof } from "./taler-crypto.js";
 import { TalerErrorCode } from "./taler-error-codes.js";
 import {
+  AccountRestriction,
   AmountString,
   AuditorDenomSig,
   CoinEnvelope,
@@ -78,8 +79,6 @@ import {
 } from "./time.js";
 import {
   OrderShortInfo,
-  TransactionMajorState,
-  TransactionMinorState,
   TransactionState,
   TransactionType,
 } from "./transactions-types.js";
@@ -1387,6 +1386,8 @@ export interface AcceptManualWithdrawalResult {
    */
   reservePub: string;
 
+  withdrawalAccountsList: WithdrawalAccountInfo[];
+
   transactionId: TransactionIdStr;
 }
 
@@ -1416,9 +1417,16 @@ export interface ManualWithdrawalDetails {
 
   /**
    * Ways to pay the exchange.
+   *
+   * @deprecated in favor of withdrawalAccountList
    */
   paytoUris: string[];
 
+  /**
+   * Ways to pay the exchange, including
+   */
+  withdrawalAccountList: WithdrawalAccountInfo[];
+
   /**
    * If the exchange supports age-restricted coins it will return
    * the array of ages.
@@ -1438,6 +1446,11 @@ export interface DenomSelectionState {
   }[];
 }
 
+export interface WireAccountDetails {
+  paytoUri: string;
+  creditRestrictions?: AccountRestriction[];
+}
+
 /**
  * Information about what will happen doing a withdrawal.
  *
@@ -1451,6 +1464,8 @@ export interface ExchangeWithdrawalDetails {
    */
   exchangeWireAccounts: string[];
 
+  withdrawalAccountList: WithdrawalAccountInfo[];
+
   /**
    * Selected denominations for withdraw.
    */
@@ -2736,3 +2751,8 @@ export interface TestingWaitTransactionRequest {
   transactionId: string;
   txState: TransactionState;
 }
+
+export interface WithdrawalAccountInfo {
+  paytoUri: string;
+  transferAmount: AmountString;
+}
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 56392f090..36ca128ae 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -164,7 +164,7 @@ export interface TalerCryptoInterface {
     req: ContractTermsValidationRequest,
   ): Promise<ValidationResult>;
 
-  createEddsaKeypair(req: unknown): Promise<EddsaKeypair>;
+  createEddsaKeypair(req: {}): Promise<EddsaKeypair>;
 
   eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
 
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 3e8452bcd..0cafae2a1 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -52,10 +52,10 @@ import {
   TalerPreciseTimestamp,
   TalerProtocolDuration,
   TalerProtocolTimestamp,
-  //TalerProtocolTimestamp,
   TransactionIdStr,
   UnblindedSignature,
   WireInfo,
+  WithdrawalAccountInfo,
   codecForAny,
 } from "@gnu-taler/taler-util";
 import { DbRetryInfo, TaskIdentifiers } from "./operations/common.js";
@@ -1373,6 +1373,11 @@ export interface WgInfoBankIntegrated {
 
 export interface WgInfoBankManual {
   withdrawalType: WithdrawalRecordType.BankManual;
+
+  /**
+   * Info about withdrawal accounts, possibly including currency conversion.
+   */
+  exchangeCreditAccounts?: WithdrawalAccountInfo[];
 }
 
 export interface WgInfoBankPeerPull {
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 7ee6275b0..b294c163e 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -720,6 +720,7 @@ function buildTransactionForManualWithdraw(
       type: WithdrawalType.ManualTransfer,
       reservePub: withdrawalGroup.reservePub,
       exchangePaytoUris,
+      exchangeCreditAccounts: withdrawalGroup.wgInfo.exchangeCreditAccounts,
       reserveIsReady:
         withdrawalGroup.status === WithdrawalGroupStatus.Done ||
         withdrawalGroup.status === WithdrawalGroupStatus.PendingReady,
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 275d0aaf0..a5a6bded8 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -24,6 +24,7 @@ import {
   AgeRestriction,
   AmountJson,
   AmountLike,
+  AmountString,
   Amounts,
   BankWithdrawDetails,
   CancellationToken,
@@ -42,6 +43,7 @@ import {
   LibtoolVersion,
   Logger,
   NotificationType,
+  PaytoUri,
   TalerError,
   TalerErrorCode,
   TalerErrorDetail,
@@ -54,21 +56,25 @@ import {
   TransactionType,
   URL,
   UnblindedSignature,
+  WireAccountDetails,
   WithdrawUriInfoResponse,
+  WithdrawalAccountInfo,
   addPaytoQueryParams,
   canonicalizeBaseUrl,
   codecForBankWithdrawalOperationPostResponse,
+  codecForCashinConversionResponse,
   codecForExchangeWithdrawBatchResponse,
   codecForIntegrationBankConfig,
   codecForReserveStatus,
   codecForWalletKycUuid,
   codecForWithdrawOperationStatusResponse,
+  createEddsaKeyPair,
   encodeCrock,
   getErrorDetailFromException,
   getRandomBytes,
   j2s,
   makeErrorDetail,
-  parseWithdrawUri
+  parseWithdrawUri,
 } from "@gnu-taler/taler-util";
 import {
   HttpRequestLibrary,
@@ -95,9 +101,8 @@ import {
 import {
   ExchangeDetailsRecord,
   ExchangeEntryDbRecordStatus,
-  PendingTaskType,
   isWithdrawableDenom,
-  timestampPreciseToDb
+  timestampPreciseToDb,
 } from "../index.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import {
@@ -110,6 +115,7 @@ import {
   makeExchangeListItem,
   runLongpollAsync,
 } from "../operations/common.js";
+import { PendingTaskType } from "../pending-types.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
 import {
   selectForcedWithdrawalDenominations,
@@ -1751,8 +1757,23 @@ export async function getExchangeWithdrawalInfo(
   logger.trace("updating exchange");
   const { exchange, exchangeDetails } =
     await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl);
+
+  if (exchangeDetails.currency != instructedAmount.currency) {
+    // Specifiying the amount in the conversion input currency is not yet 
supported.
+    // We might add support for it later.
+    throw new Error(
+      `withdrawal only supported when specifying target currency 
${exchangeDetails.currency}`,
+    );
+  }
+
+  const withdrawalAccountList = await fetchWithdrawalAccountInfo(ws, {
+    exchangeDetails,
+    instructedAmount,
+  });
+
   logger.trace("updating withdrawal denoms");
   await updateWithdrawalDenoms(ws, exchangeBaseUrl);
+
   logger.trace("getting candidate denoms");
   const denoms = await getCandidateWithdrawalDenoms(ws, exchangeBaseUrl);
   logger.trace("selecting withdrawal denoms");
@@ -1773,8 +1794,15 @@ export async function getExchangeWithdrawalInfo(
   }
 
   const exchangeWireAccounts: string[] = [];
+
   for (const account of exchangeDetails.wireInfo.accounts) {
-    exchangeWireAccounts.push(account.payto_uri);
+    const details: WireAccountDetails = {
+      paytoUri: account.payto_uri,
+    };
+    if (account.credit_restrictions) {
+      details.creditRestrictions = account.credit_restrictions;
+    }
+    exchangeWireAccounts.push(details.paytoUri);
   }
 
   let hasDenomWithAgeRestriction = false;
@@ -1854,6 +1882,7 @@ export async function getExchangeWithdrawalInfo(
     earliestDepositExpiration,
     exchangePaytoUris: paytoUris,
     exchangeWireAccounts,
+    withdrawalAccountList,
     exchangeVersion: exchangeDetails.protocolVersionRange || "unknown",
     numOfferedDenoms: possibleDenoms.length,
     selectedDenoms,
@@ -1946,15 +1975,6 @@ export async function getWithdrawalDetailsForUri(
   };
 }
 
-export async function getFundingPaytoUrisTx(
-  ws: InternalWalletState,
-  withdrawalGroupId: string,
-): Promise<string[]> {
-  return await ws.db
-    .mktx((x) => [x.exchanges, x.exchangeDetails, x.withdrawalGroups])
-    .runReadWrite((tx) => getFundingPaytoUris(tx, withdrawalGroupId));
-}
-
 export function augmentPaytoUrisForWithdrawal(
   plainPaytoUris: string[],
   reservePub: string,
@@ -2361,10 +2381,6 @@ export async function 
internalPrepareCreateWithdrawalGroup(
 
   const exchangeInfo = await updateExchangeFromUrl(ws, canonExchange);
   const exchangeDetails = exchangeInfo.exchangeDetails;
-  if (!exchangeDetails) {
-    logger.trace(exchangeDetails);
-    throw Error("exchange not updated");
-  }
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
     withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
@@ -2565,6 +2581,60 @@ export async function acceptWithdrawalFromUri(
   };
 }
 
+/**
+ * Gather information about bank accounts that can be used for
+ * withdrawals.  This includes accounts that are in a different
+ * currency and require conversion.
+ */
+async function fetchWithdrawalAccountInfo(
+  ws: InternalWalletState,
+  req: {
+    exchangeDetails: ExchangeDetailsRecord;
+    instructedAmount: AmountJson;
+    reservePub?: string;
+  },
+): Promise<WithdrawalAccountInfo[]> {
+  const { exchangeDetails, instructedAmount } = req;
+  const withdrawalAccounts: WithdrawalAccountInfo[] = [];
+  for (let acct of exchangeDetails.wireInfo.accounts) {
+    let paytoUri: string;
+    let transferAmount: AmountString;
+    if (acct.conversion_url != null) {
+      const reqUrl = new URL("cashin-rate", acct.conversion_url);
+      reqUrl.searchParams.set(
+        "amount_credit",
+        Amounts.stringify(instructedAmount),
+      );
+      const httpResp = await ws.http.fetch(reqUrl.href);
+      const resp = await readSuccessResponseJsonOrThrow(
+        httpResp,
+        codecForCashinConversionResponse(),
+      );
+      paytoUri = acct.payto_uri;
+      transferAmount = resp.amount_debit;
+      if (req.reservePub) {
+      }
+    } else {
+      paytoUri = acct.payto_uri;
+      transferAmount = Amounts.stringify(instructedAmount);
+    }
+    paytoUri = addPaytoQueryParams(paytoUri, {
+      amount: Amounts.stringify(transferAmount),
+    });
+    if (req.reservePub != null) {
+      paytoUri = addPaytoQueryParams(paytoUri, {
+        message: `Taler Withdrawal ${req.reservePub}`,
+      });
+    }
+    const acctInfo: WithdrawalAccountInfo = {
+      paytoUri,
+      transferAmount,
+    };
+    withdrawalAccounts.push(acctInfo);
+  }
+  return withdrawalAccounts;
+}
+
 /**
  * Create a manual withdrawal operation.
  *
@@ -2582,15 +2652,39 @@ export async function createManualWithdrawal(
     forcedDenomSel?: ForcedDenomSel;
   },
 ): Promise<AcceptManualWithdrawalResult> {
+  const { exchangeBaseUrl } = req;
+  const amount = Amounts.parseOrThrow(req.amount);
+  const { exchangeDetails } = await ws.exchangeOps.updateExchangeFromUrl(
+    ws,
+    exchangeBaseUrl,
+  );
+
+  if (exchangeDetails.currency != amount.currency) {
+    throw Error(
+      "manual withdrawal with conversion from foreign currency is not yet 
supported",
+    );
+  }
+  const reserveKeyPair: EddsaKeypair = await ws.cryptoApi.createEddsaKeypair(
+    {},
+  );
+
+  const withdrawalAccountList = await fetchWithdrawalAccountInfo(ws, {
+    exchangeDetails,
+    instructedAmount: amount,
+    reservePub: reserveKeyPair.pub,
+  });
+
   const withdrawalGroup = await internalCreateWithdrawalGroup(ws, {
     amount: Amounts.jsonifyAmount(req.amount),
     wgInfo: {
       withdrawalType: WithdrawalRecordType.BankManual,
+      exchangeCreditAccounts: withdrawalAccountList,
     },
     exchangeBaseUrl: req.exchangeBaseUrl,
     forcedDenomSel: req.forcedDenomSel,
     restrictAge: req.restrictAge,
     reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
+    reserveKeyPair,
   });
 
   const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
@@ -2610,6 +2704,7 @@ export async function createManualWithdrawal(
   return {
     reservePub: withdrawalGroup.reservePub,
     exchangePaytoUris: exchangePaytoUris,
+    withdrawalAccountsList: withdrawalAccountList,
     transactionId,
   };
 }
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 0694aef8a..4472bdbad 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -1215,6 +1215,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
         paytoUris: wi.exchangePaytoUris,
         tosAccepted: wi.termsOfServiceAccepted,
         ageRestrictionOptions: wi.ageRestrictionOptions,
+        withdrawalAccountList: wi.withdrawalAccountList,
         numCoins,
       };
       return resp;

-- 
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]