gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: work in progress for #8856


From: gnunet
Subject: [taler-wallet-core] branch master updated: work in progress for #8856
Date: Thu, 16 May 2024 20:45:12 +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 6d290c2fe work in progress for #8856
6d290c2fe is described below

commit 6d290c2feed8543a83d2679ed1cba53bb636b29b
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu May 16 15:45:07 2024 -0300

    work in progress for #8856
---
 .../integrationtests/test-withdrawal-handover.ts   |  17 +-
 packages/taler-util/src/wallet-types.ts            |  16 +-
 packages/taler-wallet-core/src/balance.ts          |  64 ++--
 packages/taler-wallet-core/src/db.ts               |  16 +-
 packages/taler-wallet-core/src/transactions.ts     | 114 +++++---
 packages/taler-wallet-core/src/wallet.ts           |  37 +--
 packages/taler-wallet-core/src/withdraw.ts         | 323 ++++++++++++++-------
 7 files changed, 375 insertions(+), 212 deletions(-)

diff --git 
a/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts 
b/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts
index aed266eb0..9fbdb81a4 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts
@@ -47,9 +47,10 @@ export async function runWithdrawalHandoverTest(t: 
GlobalTestState) {
     const user = await bankClient.createRandomBankUser();
     const userBankClient = new TalerCorebankApiClient(bankClient.baseUrl);
     userBankClient.setAuth(user);
+    const amount = "TESTKUDOS:10"
     const wop = await userBankClient.createWithdrawalOperation(
       user.username,
-      "TESTKUDOS:10",
+      amount,
     );
 
     const checkResp = await walletClient.call(
@@ -64,7 +65,7 @@ export async function runWithdrawalHandoverTest(t: 
GlobalTestState) {
     const prepareResp = await walletClient.call(
       WalletApiOperation.PrepareBankIntegratedWithdrawal,
       {
-        exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
+        // exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
         talerWithdrawUri: wop.taler_withdraw_uri,
       },
     );
@@ -78,6 +79,8 @@ export async function runWithdrawalHandoverTest(t: 
GlobalTestState) {
 
     await walletClient.call(WalletApiOperation.ConfirmWithdrawal, {
       transactionId: prepareResp.transactionId,
+      amount,
+      exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
     });
 
     await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
@@ -113,9 +116,11 @@ export async function runWithdrawalHandoverTest(t: 
GlobalTestState) {
     const user = await bankClient.createRandomBankUser();
     const userBankClient = new TalerCorebankApiClient(bankClient.baseUrl);
     userBankClient.setAuth(user);
+    const amount = "TESTKUDOS:10";
+
     const wop = await userBankClient.createWithdrawalOperation(
       user.username,
-      "TESTKUDOS:10",
+      amount,
     );
 
     const checkResp = await walletClient.call(
@@ -130,7 +135,7 @@ export async function runWithdrawalHandoverTest(t: 
GlobalTestState) {
     const prepareRespW1 = await walletClient.call(
       WalletApiOperation.PrepareBankIntegratedWithdrawal,
       {
-        exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
+        // exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
         talerWithdrawUri: wop.taler_withdraw_uri,
       },
     );
@@ -138,13 +143,15 @@ export async function runWithdrawalHandoverTest(t: 
GlobalTestState) {
     const prepareRespW2 = await w2.walletClient.call(
       WalletApiOperation.PrepareBankIntegratedWithdrawal,
       {
-        exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
+        // exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
         talerWithdrawUri: wop.taler_withdraw_uri,
       },
     );
 
     await w2.walletClient.call(WalletApiOperation.ConfirmWithdrawal, {
       transactionId: prepareRespW2.transactionId,
+      amount,
+      exchangeBaseUrl: checkResp.defaultExchangeBaseUrl,
     });
 
     await w2.walletClient.call(WalletApiOperation.TestingWaitTransactionState, 
{
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 310ca858e..799cbc601 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -526,7 +526,7 @@ export interface WalletRunConfig {
   /**
    * Start processing tasks only when explicitly required, even after
    * init has been called.
-   * 
+   *
    * Useful when the wallet is started to make single read-only request,
    * as otherwise wallet-core starts making network request and process
    * unrelated pending tasks.
@@ -1845,18 +1845,12 @@ export interface GetWithdrawalDetailsForAmountRequest {
 
 export interface PrepareBankIntegratedWithdrawalRequest {
   talerWithdrawUri: string;
-  exchangeBaseUrl: string;
-  forcedDenomSel?: ForcedDenomSel;
-  restrictAge?: number;
 }
 
 export const codecForPrepareBankIntegratedWithdrawalRequest =
   (): Codec<PrepareBankIntegratedWithdrawalRequest> =>
     buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>()
-      .property("exchangeBaseUrl", codecForCanonBaseUrl())
       .property("talerWithdrawUri", codecForString())
-      .property("forcedDenomSel", codecForAny())
-      .property("restrictAge", codecOptional(codecForNumber()))
       .build("PrepareBankIntegratedWithdrawalRequest");
 
 export interface PrepareBankIntegratedWithdrawalResponse {
@@ -1865,12 +1859,20 @@ export interface 
PrepareBankIntegratedWithdrawalResponse {
 
 export interface ConfirmWithdrawalRequest {
   transactionId: string;
+  exchangeBaseUrl: string;
+  amount: AmountString;
+  forcedDenomSel?: ForcedDenomSel;
+  restrictAge?: number;
 }
 
 export const codecForConfirmWithdrawalRequestRequest =
   (): Codec<ConfirmWithdrawalRequest> =>
     buildCodecForObject<ConfirmWithdrawalRequest>()
       .property("transactionId", codecForString())
+      .property("amount", codecForAmountString())
+      .property("exchangeBaseUrl", codecForCanonBaseUrl())
+      .property("forcedDenomSel", codecForAny())
+      .property("restrictAge", codecOptional(codecForNumber()))
       .build("ConfirmWithdrawalRequest");
 
 export interface AcceptBankIntegratedWithdrawalRequest {
diff --git a/packages/taler-wallet-core/src/balance.ts 
b/packages/taler-wallet-core/src/balance.ts
index 5a805b477..e4783350c 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -57,6 +57,7 @@ import {
   assertUnreachable,
   BalanceFlag,
   BalancesResponse,
+  checkDbInvariant,
   GetBalanceDetailRequest,
   j2s,
   Logger,
@@ -350,9 +351,8 @@ export async function getBalancesInsideTransaction(
 
   await tx.withdrawalGroups.indexes.byStatus
     .iter(keyRangeActive)
-    .forEachAsync(async (wgRecord) => {
-      const currency = Amounts.currencyOf(wgRecord.denomsSel.totalCoinValue);
-      switch (wgRecord.status) {
+    .forEachAsync(async (wg) => {
+      switch (wg.status) {
         case WithdrawalGroupStatus.AbortedBank:
         case WithdrawalGroupStatus.AbortedExchange:
         case WithdrawalGroupStatus.FailedAbortingBank:
@@ -374,34 +374,54 @@ export async function getBalancesInsideTransaction(
           // Pending, but no special flag.
           break;
         case WithdrawalGroupStatus.SuspendedKyc:
-        case WithdrawalGroupStatus.PendingKyc:
-          await balanceStore.setFlagIncomingKyc(
-            currency,
-            wgRecord.exchangeBaseUrl,
-          );
+        case WithdrawalGroupStatus.PendingKyc: {
+          checkDbInvariant(wg.denomsSel !== undefined, "wg in kyc state should 
have been initialized")
+          checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg in kyc state 
should have been initialized")
+          const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
+          await balanceStore.setFlagIncomingKyc(currency, wg.exchangeBaseUrl);
           break;
+        }
         case WithdrawalGroupStatus.PendingAml:
-        case WithdrawalGroupStatus.SuspendedAml:
-          await balanceStore.setFlagIncomingAml(
-            currency,
-            wgRecord.exchangeBaseUrl,
-          );
+        case WithdrawalGroupStatus.SuspendedAml: {
+          checkDbInvariant(wg.denomsSel !== undefined, "wg in aml state should 
have been initialized")
+          checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg in aml state 
should have been initialized")
+          const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
+          await balanceStore.setFlagIncomingAml(currency, wg.exchangeBaseUrl);
+          break;
+        }
+        case WithdrawalGroupStatus.PendingRegisteringBank: {
+          if (wg.denomsSel && wg.exchangeBaseUrl) {
+            const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
+            await balanceStore.setFlagIncomingConfirmation(
+              currency,
+              wg.exchangeBaseUrl,
+            );
+          }
           break;
-        case WithdrawalGroupStatus.PendingRegisteringBank:
-        case WithdrawalGroupStatus.PendingWaitConfirmBank:
+        }
+        case WithdrawalGroupStatus.PendingWaitConfirmBank: {
+          checkDbInvariant(wg.denomsSel !== undefined, "wg in confirmed state 
should have been initialized")
+          checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg in confirmed 
state should have been initialized")
+          const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
           await balanceStore.setFlagIncomingConfirmation(
             currency,
-            wgRecord.exchangeBaseUrl,
+            wg.exchangeBaseUrl,
           );
           break;
+        }
         default:
-          assertUnreachable(wgRecord.status);
+          assertUnreachable(wg.status);
       }
-      await balanceStore.addPendingIncoming(
-        currency,
-        wgRecord.exchangeBaseUrl,
-        wgRecord.denomsSel.totalCoinValue,
-      );
+      if (wg.denomsSel && wg.exchangeBaseUrl) {
+        // only inform pending incoming if amount and exchange has been 
selected
+        const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
+        await balanceStore.addPendingIncoming(
+          currency,
+          wg.exchangeBaseUrl,
+          wg.denomsSel.totalCoinValue,
+        );
+      }
+
     });
 
   await tx.peerPushDebit.indexes.byStatus
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index b75e48c39..e5bc1c9e9 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -376,7 +376,7 @@ export interface ReserveBankInfo {
   /**
    * Exchange payto URI that the bank will use to fund the reserve.
    */
-  exchangePaytoUri: string;
+  exchangePaytoUri?: string;
 
   /**
    * Time when the information about this reserve was posted to the bank.
@@ -1528,7 +1528,7 @@ export interface WithdrawalGroupRecord {
    * The exchange base URL that we're withdrawing from.
    * (Redundantly stored, as the reserve record also has this info.)
    */
-  exchangeBaseUrl: string;
+  exchangeBaseUrl?: string;
 
   /**
    * When was the withdrawal operation started started?
@@ -1562,7 +1562,7 @@ export interface WithdrawalGroupRecord {
   /**
    * Amount that was sent by the user to fund the reserve.
    */
-  instructedAmount: AmountString;
+  instructedAmount?: AmountString;
 
   /**
    * Amount that was observed when querying the reserve that
@@ -1579,7 +1579,7 @@ export interface WithdrawalGroupRecord {
    * (Initial amount confirmed by the user, might differ with denomSel
    * on reselection.)
    */
-  rawWithdrawalAmount: AmountString;
+  rawWithdrawalAmount?: AmountString;
 
   /**
    * Amount that will be added to the balance when the withdrawal succeeds.
@@ -1587,12 +1587,12 @@ export interface WithdrawalGroupRecord {
    * (Initial amount confirmed by the user, might differ with denomSel
    * on reselection.)
    */
-  effectiveWithdrawalAmount: AmountString;
+  effectiveWithdrawalAmount?: AmountString;
 
   /**
    * Denominations selected for withdrawal.
    */
-  denomsSel: DenomSelectionState;
+  denomsSel?: DenomSelectionState;
 
   /**
    * UID of the denomination selection.
@@ -2677,7 +2677,9 @@ export const WalletStoresV1 = {
     describeContents<BankWithdrawUriRecord>({
       keyPath: "talerWithdrawUri",
     }),
-    {},
+    {
+      byGroup: describeIndex("byGroup", "withdrawalGroupId"),
+    },
   ),
   backupProviders: describeStore(
     "backupProviders",
diff --git a/packages/taler-wallet-core/src/transactions.ts 
b/packages/taler-wallet-core/src/transactions.ts
index 12a4a31b5..f36380033 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -252,6 +252,10 @@ export async function getTransactionById(
               ort,
             );
           }
+          checkDbInvariant(
+            withdrawalGroupRecord.exchangeBaseUrl !== undefined,
+            "manual withdraw should have exchange url",
+          );
           const exchangeDetails = await getExchangeWireDetailsInTx(
             tx,
             withdrawalGroupRecord.exchangeBaseUrl,
@@ -589,6 +593,9 @@ function buildTransactionForPeerPullCredit(
         );
       });
     const txState = computePeerPullCreditTransactionState(pullCredit);
+    checkDbInvariant(wsr.instructedAmount !== undefined, "wg unitialized");
+    checkDbInvariant(wsr.denomsSel !== undefined, "wg unitialized");
+    checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg unitialized");
     return {
       type: TransactionType.PeerPullCredit,
       txState,
@@ -654,13 +661,16 @@ function buildTransactionForPeerPushCredit(
   pushInc: PeerPushPaymentIncomingRecord,
   pushOrt: OperationRetryRecord | undefined,
   peerContractTerms: PeerContractTerms,
-  wsr: WithdrawalGroupRecord | undefined,
+  wg: WithdrawalGroupRecord | undefined,
   wsrOrt: OperationRetryRecord | undefined,
 ): Transaction {
-  if (wsr) {
-    if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) {
+  if (wg) {
+    if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) {
       throw Error("invalid withdrawal group type for push payment credit");
     }
+    checkDbInvariant(wg.instructedAmount !== undefined, "wg unitialized");
+    checkDbInvariant(wg.denomsSel !== undefined, "wg unitialized");
+    checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg unitialized");
 
     const txState = computePeerPushCreditTransactionState(pushInc);
     return {
@@ -668,15 +678,15 @@ function buildTransactionForPeerPushCredit(
       txState,
       txActions: computePeerPushCreditTransactionActions(pushInc),
       amountEffective: isUnsuccessfulTransaction(txState)
-        ? Amounts.stringify(Amounts.zeroOfAmount(wsr.instructedAmount))
-        : Amounts.stringify(wsr.denomsSel.totalCoinValue),
-      amountRaw: Amounts.stringify(wsr.instructedAmount),
-      exchangeBaseUrl: wsr.exchangeBaseUrl,
+        ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
+        : Amounts.stringify(wg.denomsSel.totalCoinValue),
+      amountRaw: Amounts.stringify(wg.instructedAmount),
+      exchangeBaseUrl: wg.exchangeBaseUrl,
       info: {
         expiration: peerContractTerms.purse_expiration,
         summary: peerContractTerms.summary,
       },
-      timestamp: timestampPreciseFromDb(wsr.timestampStart),
+      timestamp: timestampPreciseFromDb(wg.timestampStart),
       transactionId: constructTransactionIdentifier({
         tag: TransactionType.PeerPushCredit,
         peerPushCreditId: pushInc.peerPushCreditId,
@@ -712,37 +722,40 @@ function buildTransactionForPeerPushCredit(
 }
 
 function buildTransactionForBankIntegratedWithdraw(
-  wgRecord: WithdrawalGroupRecord,
+  wg: WithdrawalGroupRecord,
   ort?: OperationRetryRecord,
 ): TransactionWithdrawal {
-  if (wgRecord.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated)
+  if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated)
     throw Error("");
 
-  const txState = computeWithdrawalTransactionStatus(wgRecord);
+  const txState = computeWithdrawalTransactionStatus(wg);
+  checkDbInvariant(wg.instructedAmount !== undefined, "wg unitialized");
+  checkDbInvariant(wg.denomsSel !== undefined, "wg unitialized");
+  checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg unitialized");
   return {
     type: TransactionType.Withdrawal,
     txState,
-    txActions: computeWithdrawalTransactionActions(wgRecord),
+    txActions: computeWithdrawalTransactionActions(wg),
     amountEffective: isUnsuccessfulTransaction(txState)
-      ? Amounts.stringify(Amounts.zeroOfAmount(wgRecord.instructedAmount))
-      : Amounts.stringify(wgRecord.denomsSel.totalCoinValue),
-    amountRaw: Amounts.stringify(wgRecord.instructedAmount),
+      ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
+      : Amounts.stringify(wg.denomsSel.totalCoinValue),
+    amountRaw: Amounts.stringify(wg.instructedAmount),
     withdrawalDetails: {
       type: WithdrawalType.TalerBankIntegrationApi,
-      confirmed: wgRecord.wgInfo.bankInfo.timestampBankConfirmed ? true : 
false,
-      exchangeCreditAccountDetails: wgRecord.wgInfo.exchangeCreditAccounts,
-      reservePub: wgRecord.reservePub,
-      bankConfirmationUrl: wgRecord.wgInfo.bankInfo.confirmUrl,
+      confirmed: wg.wgInfo.bankInfo.timestampBankConfirmed ? true : false,
+      exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
+      reservePub: wg.reservePub,
+      bankConfirmationUrl: wg.wgInfo.bankInfo.confirmUrl,
       reserveIsReady:
-        wgRecord.status === WithdrawalGroupStatus.Done ||
-        wgRecord.status === WithdrawalGroupStatus.PendingReady,
+        wg.status === WithdrawalGroupStatus.Done ||
+        wg.status === WithdrawalGroupStatus.PendingReady,
     },
-    kycUrl: wgRecord.kycUrl,
-    exchangeBaseUrl: wgRecord.exchangeBaseUrl,
-    timestamp: timestampPreciseFromDb(wgRecord.timestampStart),
+    kycUrl: wg.kycUrl,
+    exchangeBaseUrl: wg.exchangeBaseUrl,
+    timestamp: timestampPreciseFromDb(wg.timestampStart),
     transactionId: constructTransactionIdentifier({
       tag: TransactionType.Withdrawal,
-      withdrawalGroupId: wgRecord.withdrawalGroupId,
+      withdrawalGroupId: wg.withdrawalGroupId,
     }),
     ...(ort?.lastError ? { error: ort.lastError } : {}),
   };
@@ -759,50 +772,50 @@ export function isUnsuccessfulTransaction(state: 
TransactionState): boolean {
 }
 
 function buildTransactionForManualWithdraw(
-  withdrawalGroup: WithdrawalGroupRecord,
+  wg: WithdrawalGroupRecord,
   exchangeDetails: ExchangeWireDetails,
   ort?: OperationRetryRecord,
 ): TransactionWithdrawal {
-  if (withdrawalGroup.wgInfo.withdrawalType !== 
WithdrawalRecordType.BankManual)
+  if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual)
     throw Error("");
 
   const plainPaytoUris =
     exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
 
+  checkDbInvariant(wg.instructedAmount !== undefined, "wg unitialized");
+  checkDbInvariant(wg.denomsSel !== undefined, "wg unitialized");
+  checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg unitialized");
   const exchangePaytoUris = augmentPaytoUrisForWithdrawal(
     plainPaytoUris,
-    withdrawalGroup.reservePub,
-    withdrawalGroup.instructedAmount,
+    wg.reservePub,
+    wg.instructedAmount,
   );
 
-  const txState = computeWithdrawalTransactionStatus(withdrawalGroup);
+  const txState = computeWithdrawalTransactionStatus(wg);
 
   return {
     type: TransactionType.Withdrawal,
     txState,
-    txActions: computeWithdrawalTransactionActions(withdrawalGroup),
+    txActions: computeWithdrawalTransactionActions(wg),
     amountEffective: isUnsuccessfulTransaction(txState)
-      ? Amounts.stringify(
-          Amounts.zeroOfAmount(withdrawalGroup.instructedAmount),
-        )
-      : Amounts.stringify(withdrawalGroup.denomsSel.totalCoinValue),
-    amountRaw: Amounts.stringify(withdrawalGroup.instructedAmount),
+      ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
+      : Amounts.stringify(wg.denomsSel.totalCoinValue),
+    amountRaw: Amounts.stringify(wg.instructedAmount),
     withdrawalDetails: {
       type: WithdrawalType.ManualTransfer,
-      reservePub: withdrawalGroup.reservePub,
+      reservePub: wg.reservePub,
       exchangePaytoUris,
-      exchangeCreditAccountDetails:
-        withdrawalGroup.wgInfo.exchangeCreditAccounts,
+      exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
       reserveIsReady:
-        withdrawalGroup.status === WithdrawalGroupStatus.Done ||
-        withdrawalGroup.status === WithdrawalGroupStatus.PendingReady,
+        wg.status === WithdrawalGroupStatus.Done ||
+        wg.status === WithdrawalGroupStatus.PendingReady,
     },
-    kycUrl: withdrawalGroup.kycUrl,
-    exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
-    timestamp: timestampPreciseFromDb(withdrawalGroup.timestampStart),
+    kycUrl: wg.kycUrl,
+    exchangeBaseUrl: wg.exchangeBaseUrl,
+    timestamp: timestampPreciseFromDb(wg.timestampStart),
     transactionId: constructTransactionIdentifier({
       tag: TransactionType.Withdrawal,
-      withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
+      withdrawalGroupId: wg.withdrawalGroupId,
     }),
     ...(ort?.lastError ? { error: ort.lastError } : {}),
   };
@@ -1090,6 +1103,10 @@ export async function getWithdrawalTransactionByUri(
           ort,
         );
       }
+      checkDbInvariant(
+        withdrawalGroupRecord.exchangeBaseUrl !== undefined,
+        "manual withdraw should have exchange url",
+      );
       const exchangeDetails = await getExchangeWireDetailsInTx(
         tx,
         withdrawalGroupRecord.exchangeBaseUrl,
@@ -1337,6 +1354,13 @@ export async function getTransactions(
       });
 
       await iterRecordsForWithdrawal(tx, filter, async (wsr) => {
+        if (
+          wsr.rawWithdrawalAmount === undefined ||
+          wsr.exchangeBaseUrl == undefined
+        ) {
+          // skip prepared withdrawals which has not been confirmed
+          return;
+        }
         const exchangesInTx = [wsr.exchangeBaseUrl];
         if (
           shouldSkipCurrency(
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 336817be9..eee89b483 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -84,7 +84,6 @@ import {
   codecForAny,
   codecForApplyDevExperiment,
   codecForCanonicalizeBaseUrlRequest,
-  codecForCheckPayTemplateRequest,
   codecForCheckPeerPullPaymentRequest,
   codecForCheckPeerPushDebitRequest,
   codecForConfirmPayRequest,
@@ -107,7 +106,6 @@ import {
   codecForGetExchangeTosRequest,
   codecForGetWithdrawalDetailsForAmountRequest,
   codecForGetWithdrawalDetailsForUri,
-  codecForHintNetworkAvailabilityRequest,
   codecForImportDbRequest,
   codecForInitRequest,
   codecForInitiatePeerPullPaymentRequest,
@@ -230,7 +228,6 @@ import {
   observeTalerCrypto,
 } from "./observable-wrappers.js";
 import {
-  checkPayForTemplate,
   confirmPay,
   getContractTermsDetails,
   preparePayForTemplate,
@@ -288,7 +285,6 @@ import {
   getWithdrawalTransactionByUri,
   parseTransactionIdentifier,
   resumeTransaction,
-  retryAll,
   retryTransaction,
   suspendTransaction,
 } from "./transactions.js";
@@ -746,6 +742,7 @@ async function dispatchRequestInternal(
           innerError: getErrorDetailFromException(e),
         });
       }
+
       wex.ws.initWithConfig(applyRunConfigDefaults(req.config));
 
       if (wex.ws.config.testing.skipDefaults) {
@@ -758,11 +755,8 @@ async function dispatchRequestInternal(
         versionInfo: getVersion(wex),
       };
 
-      if (req.config?.lazyTaskLoop) {
-        logger.trace("lazily starting task loop");
-      } else {
-        await wex.taskScheduler.ensureRunning();
-      }
+      // After initialization, task loop should run.
+      await wex.taskScheduler.ensureRunning();
 
       wex.ws.initCalled = true;
       return resp;
@@ -1006,16 +1000,13 @@ async function dispatchRequestInternal(
     }
     case WalletApiOperation.ConfirmWithdrawal: {
       const req = codecForConfirmWithdrawalRequestRequest().decode(payload);
-      return confirmWithdrawal(wex, req.transactionId);
+      return confirmWithdrawal(wex, req);
     }
     case WalletApiOperation.PrepareBankIntegratedWithdrawal: {
       const req =
         codecForPrepareBankIntegratedWithdrawalRequest().decode(payload);
       return prepareBankIntegratedWithdrawal(wex, {
-        selectedExchange: req.exchangeBaseUrl,
         talerWithdrawUri: req.talerWithdrawUri,
-        forcedDenomSel: req.forcedDenomSel,
-        restrictAge: req.restrictAge,
       });
     }
     case WalletApiOperation.GetExchangeTos: {
@@ -1054,10 +1045,6 @@ async function dispatchRequestInternal(
       const req = codecForPrepareWithdrawExchangeRequest().decode(payload);
       return handlePrepareWithdrawExchange(wex, req);
     }
-    case WalletApiOperation.CheckPayForTemplate: {
-      const req = codecForCheckPayTemplateRequest().decode(payload);
-      return await checkPayForTemplate(wex, req);
-    }
     case WalletApiOperation.PreparePayForUri: {
       const req = codecForPreparePayRequest().decode(payload);
       return await preparePayForUri(wex, req.talerPayUri);
@@ -1242,16 +1229,10 @@ async function dispatchRequestInternal(
       await loadBackupRecovery(wex, req);
       return {};
     }
-    case WalletApiOperation.HintNetworkAvailability: {
-      const req = codecForHintNetworkAvailabilityRequest().decode(payload);
-      if (req.isNetworkAvailable) {
-        await retryAll(wex);
-      } else {
-        // We're not doing anything right now, but we could stop showing
-        // certain errors!
-      }
-      return {};
-    }
+    // case WalletApiOperation.GetPlanForOperation: {
+    //   const req = codecForGetPlanForOperationRequest().decode(payload);
+    //   return await getPlanForOperation(ws, req);
+    // }
     case WalletApiOperation.ConvertDepositAmount: {
       const req = codecForConvertAmountRequest.decode(payload);
       return await convertDepositAmount(wex, req);
@@ -1846,7 +1827,7 @@ class WalletDbTriggerSpec implements TriggerSpec {
     if (info.mode !== "readwrite") {
       return;
     }
-    logger.trace(
+    logger.info(
       `in after commit callback for readwrite, modified ${j2s([
         ...info.modifiedStores,
       ])}`,
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index 0eb9a3dfe..d55eebf45 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -36,6 +36,7 @@ import {
   BankWithdrawDetails,
   CancellationToken,
   CoinStatus,
+  ConfirmWithdrawalRequest,
   CurrencySpecification,
   DenomKeyType,
   DenomSelItem,
@@ -194,6 +195,15 @@ async function updateWithdrawalTransaction(
 
   let transactionItem: Transaction;
 
+  if (
+    !wgRecord.instructedAmount ||
+    !wgRecord.denomsSel ||
+    !wgRecord.exchangeBaseUrl
+  ) {
+    // withdrawal group is in preparation, nothing to update
+    return;
+  }
+
   if (wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated) {
     const txState = computeWithdrawalTransactionStatus(wgRecord);
     transactionItem = {
@@ -224,6 +234,18 @@ async function updateWithdrawalTransaction(
   } else if (
     wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankManual
   ) {
+    checkDbInvariant(
+      wgRecord.exchangeBaseUrl !== undefined,
+      "manual withdrawal without exchange can't be created",
+    );
+    checkDbInvariant(
+      wgRecord.instructedAmount !== undefined,
+      "manual withdrawal without amount can't be created",
+    );
+    checkDbInvariant(
+      wgRecord.denomsSel !== undefined,
+      "manual withdrawal without denoms can't be created",
+    );
     const exchangeDetails = await getExchangeWireDetailsInTx(
       tx,
       wgRecord.exchangeBaseUrl,
@@ -895,6 +917,15 @@ async function processPlanchetGenerate(
   withdrawalGroup: WithdrawalGroupRecord,
   coinIdx: number,
 ): Promise<void> {
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't process unitialized exchange",
+  );
+  checkDbInvariant(
+    withdrawalGroup.denomsSel !== undefined,
+    "can't process unitialized exchange",
+  );
+  const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
   let planchet = await wex.db.runReadOnlyTx(
     { storeNames: ["planchets"] },
     async (tx) => {
@@ -932,12 +963,7 @@ async function processPlanchetGenerate(
   const denom = await wex.db.runReadOnlyTx(
     { storeNames: ["denominations"] },
     async (tx) => {
-      return getDenomInfo(
-        wex,
-        tx,
-        withdrawalGroup.exchangeBaseUrl,
-        denomPubHash,
-      );
+      return getDenomInfo(wex, tx, exchangeBaseUrl, denomPubHash);
     },
   );
   checkDbInvariant(!!denom);
@@ -1058,7 +1084,7 @@ async function handleKycRequired(
         return TransitionResult.stay();
       }
       for (let i = startIdx; i < requestCoinIdxs.length; i++) {
-        let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+        const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
           withdrawalGroup.withdrawalGroupId,
           requestCoinIdxs[i],
         ]);
@@ -1103,6 +1129,11 @@ async function processPlanchetExchangeBatchRequest(
   logger.info(
     `processing planchet exchange batch request 
${withdrawalGroup.withdrawalGroupId}, start=${args.coinStartIndex}, 
len=${args.batchSize}`,
   );
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't process unitialized exchange",
+  );
+  const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
 
   const batchReq: ExchangeBatchWithdrawRequest = { planchets: [] };
   // Indices of coins that are included in the batch request
@@ -1117,7 +1148,7 @@ async function processPlanchetExchangeBatchRequest(
         coinIdx < wgContext.numPlanchets;
         coinIdx++
       ) {
-        let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+        const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
           withdrawalGroup.withdrawalGroupId,
           coinIdx,
         ]);
@@ -1134,7 +1165,7 @@ async function processPlanchetExchangeBatchRequest(
         const denom = await getDenomInfo(
           wex,
           tx,
-          withdrawalGroup.exchangeBaseUrl,
+          exchangeBaseUrl,
           planchet.denomPubHash,
         );
 
@@ -1168,7 +1199,7 @@ async function processPlanchetExchangeBatchRequest(
   ): Promise<void> {
     logger.trace(`withdrawal request failed: ${j2s(errDetail)}`);
     await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => {
-      let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+      const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
         withdrawalGroup.withdrawalGroupId,
         coinIdx,
       ]);
@@ -1237,11 +1268,17 @@ async function processPlanchetVerifyAndStoreCoin(
   resp: ExchangeWithdrawResponse,
 ): Promise<void> {
   const withdrawalGroup = wgContext.wgRecord;
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't process unitialized exchange",
+  );
+  const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
+
   logger.trace(`checking and storing planchet idx=${coinIdx}`);
   const d = await wex.db.runReadOnlyTx(
     { storeNames: ["planchets", "denominations"] },
     async (tx) => {
-      let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+      const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
         withdrawalGroup.withdrawalGroupId,
         coinIdx,
       ]);
@@ -1255,7 +1292,7 @@ async function processPlanchetVerifyAndStoreCoin(
       const denomInfo = await getDenomInfo(
         wex,
         tx,
-        withdrawalGroup.exchangeBaseUrl,
+        exchangeBaseUrl,
         planchet.denomPubHash,
       );
       if (!denomInfo) {
@@ -1264,7 +1301,7 @@ async function processPlanchetVerifyAndStoreCoin(
       return {
         planchet,
         denomInfo,
-        exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
+        exchangeBaseUrl: exchangeBaseUrl,
       };
     },
   );
@@ -1285,7 +1322,7 @@ async function processPlanchetVerifyAndStoreCoin(
     throw Error(`cipher (${planchetDenomPub.cipher}) not supported`);
   }
 
-  let evSig = resp.ev_sig;
+  const evSig = resp.ev_sig;
   if (!(evSig.cipher === DenomKeyType.Rsa)) {
     throw Error("unsupported cipher");
   }
@@ -1304,7 +1341,7 @@ async function processPlanchetVerifyAndStoreCoin(
 
   if (!isValid) {
     await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => {
-      let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
+      const planchet = await tx.planchets.indexes.byGroupAndIndex.get([
         withdrawalGroup.withdrawalGroupId,
         coinIdx,
       ]);
@@ -1483,6 +1520,19 @@ async function processQueryReserve(
   if (withdrawalGroup.status !== WithdrawalGroupStatus.PendingQueryingStatus) {
     return TaskRunResult.backoff();
   }
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't process unitialized exchange",
+  );
+  checkDbInvariant(
+    withdrawalGroup.denomsSel !== undefined,
+    "can't process unitialized exchange",
+  );
+  checkDbInvariant(
+    withdrawalGroup.instructedAmount !== undefined,
+    "can't process unitialized exchange",
+  );
+
   const reservePub = withdrawalGroup.reservePub;
 
   const reserveUrl = new URL(
@@ -1705,6 +1755,14 @@ async function redenominateWithdrawal(
       if (!wg) {
         return;
       }
+      checkDbInvariant(
+        wg.exchangeBaseUrl !== undefined,
+        "can't process unitialized exchange",
+      );
+      checkDbInvariant(
+        wg.denomsSel !== undefined,
+        "can't process unitialized exchange",
+      );
       const currency = Amounts.currencyOf(wg.denomsSel.totalWithdrawCost);
       const exchangeBaseUrl = wg.exchangeBaseUrl;
 
@@ -1721,13 +1779,13 @@ async function redenominateWithdrawal(
         logger.trace(`old denom sel: ${j2s(oldSel)}`);
       }
 
-      let zero = Amount.zeroOfCurrency(currency);
+      const zero = Amount.zeroOfCurrency(currency);
       let amountRemaining = zero;
       let prevTotalCoinValue = zero;
       let prevTotalWithdrawalCost = zero;
       let prevHasDenomWithAgeRestriction = false;
       let prevEarliestDepositExpiration = AbsoluteTime.never();
-      let prevDenoms: DenomSelItem[] = [];
+      const prevDenoms: DenomSelItem[] = [];
       let coinIndex = 0;
       for (let i = 0; i < oldSel.selectedDenoms.length; i++) {
         const sel = wg.denomsSel.selectedDenoms[i];
@@ -1739,7 +1797,7 @@ async function redenominateWithdrawal(
           throw Error("denom in use but not not found");
         }
         // FIXME: Also check planchet if there was a different error or 
planchet already withdrawn
-        let denomOkay = isWithdrawableDenom(
+        const denomOkay = isWithdrawableDenom(
           denom,
           wex.ws.config.testing.denomselAllowLate,
         );
@@ -1840,8 +1898,15 @@ async function processWithdrawalGroupPendingReady(
   const { withdrawalGroupId } = withdrawalGroup;
   const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
 
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't process unitialized exchange",
+  );
+  checkDbInvariant(
+    withdrawalGroup.denomsSel !== undefined,
+    "can't process unitialized exchange",
+  );
   const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
-
   await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl);
 
   if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) {
@@ -1943,7 +2008,6 @@ async function processWithdrawalGroupPendingReady(
   const errorsPerCoin: Record<number, TalerErrorDetail> = {};
   let numPlanchetErrors = 0;
   let numActive = 0;
-  let numDone = 0;
   const maxReportedErrors = 5;
 
   const res = await ctx.transition(
@@ -1964,7 +2028,6 @@ async function processWithdrawalGroupPendingReady(
             numActive++;
             break;
           case PlanchetStatus.WithdrawalDone:
-            numDone++;
             break;
         }
         if (x.lastError) {
@@ -2269,6 +2332,14 @@ export async function getFundingPaytoUris(
 ): Promise<string[]> {
   const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
   checkDbInvariant(!!withdrawalGroup);
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
+  checkDbInvariant(
+    withdrawalGroup.instructedAmount !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
   const exchangeDetails = await getExchangeWireDetailsInTx(
     tx,
     withdrawalGroup.exchangeBaseUrl,
@@ -2579,12 +2650,41 @@ export interface PrepareCreateWithdrawalGroupResult {
   };
 }
 
+async function getInitialDenomsSelection(
+  wex: WalletExecutionContext,
+  exchange: string,
+  amount: AmountJson,
+  forcedDenoms: ForcedDenomSel | undefined,
+): Promise<DenomSelectionState> {
+  const currency = Amounts.currencyOf(amount);
+  await updateWithdrawalDenoms(wex, exchange);
+  const denoms = await getCandidateWithdrawalDenoms(wex, exchange, currency);
+
+  if (forcedDenoms) {
+    logger.warn("using forced denom selection");
+    const initialDenomSel = selectForcedWithdrawalDenominations(
+      amount,
+      denoms,
+      forcedDenoms,
+      wex.ws.config.testing.denomselAllowLate,
+    );
+    return initialDenomSel;
+  } else {
+    const initialDenomSel = selectWithdrawalDenominations(
+      amount,
+      denoms,
+      wex.ws.config.testing.denomselAllowLate,
+    );
+    return initialDenomSel;
+  }
+}
+
 export async function internalPrepareCreateWithdrawalGroup(
   wex: WalletExecutionContext,
   args: {
     reserveStatus: WithdrawalGroupStatus;
-    amount: AmountJson;
-    exchangeBaseUrl: string;
+    amount?: AmountJson;
+    exchangeBaseUrl?: string;
     forcedWithdrawalGroupId?: string;
     forcedDenomSel?: ForcedDenomSel;
     reserveKeyPair?: EddsaKeypair;
@@ -2598,9 +2698,8 @@ export async function 
internalPrepareCreateWithdrawalGroup(
   const secretSeed = encodeCrock(getRandomBytes(32));
   const exchangeBaseUrl = args.exchangeBaseUrl;
   const amount = args.amount;
-  const currency = Amounts.currencyOf(amount);
 
-  let withdrawalGroupId;
+  let withdrawalGroupId: string;
 
   if (args.forcedWithdrawalGroupId) {
     withdrawalGroupId = args.forcedWithdrawalGroupId;
@@ -2623,39 +2722,37 @@ export async function 
internalPrepareCreateWithdrawalGroup(
     withdrawalGroupId = encodeCrock(getRandomBytes(32));
   }
 
-  await updateWithdrawalDenoms(wex, exchangeBaseUrl);
-  const denoms = await getCandidateWithdrawalDenoms(
-    wex,
-    exchangeBaseUrl,
-    currency,
-  );
-
-  let initialDenomSel: DenomSelectionState;
+  let initialDenomSel: DenomSelectionState | undefined;
   const denomSelUid = encodeCrock(getRandomBytes(16));
-  if (args.forcedDenomSel) {
-    logger.warn("using forced denom selection");
-    initialDenomSel = selectForcedWithdrawalDenominations(
-      amount,
-      denoms,
+
+  const creationInfo =
+    exchangeBaseUrl !== undefined && amount !== undefined
+      ? {
+          canonExchange: exchangeBaseUrl,
+          amount,
+        }
+      : undefined;
+
+  if (creationInfo) {
+    initialDenomSel = await getInitialDenomsSelection(
+      wex,
+      creationInfo.canonExchange,
+      creationInfo.amount,
       args.forcedDenomSel,
-      wex.ws.config.testing.denomselAllowLate,
-    );
-  } else {
-    initialDenomSel = selectWithdrawalDenominations(
-      amount,
-      denoms,
-      wex.ws.config.testing.denomselAllowLate,
     );
   }
 
   const withdrawalGroup: WithdrawalGroupRecord = {
     denomSelUid,
+    // next fields will be undefined if exchange or amount is not specified
     denomsSel: initialDenomSel,
     exchangeBaseUrl: exchangeBaseUrl,
-    instructedAmount: Amounts.stringify(amount),
+    instructedAmount:
+      amount === undefined ? undefined : Amounts.stringify(amount),
+    rawWithdrawalAmount: initialDenomSel?.totalWithdrawCost,
+    effectiveWithdrawalAmount: initialDenomSel?.totalCoinValue,
+    // end of optional fields
     timestampStart: timestampPreciseToDb(now),
-    rawWithdrawalAmount: initialDenomSel.totalWithdrawCost,
-    effectiveWithdrawalAmount: initialDenomSel.totalCoinValue,
     secretSeed,
     reservePriv: reserveKeyPair.priv,
     reservePub: reserveKeyPair.pub,
@@ -2667,7 +2764,10 @@ export async function 
internalPrepareCreateWithdrawalGroup(
     wgInfo: args.wgInfo,
   };
 
-  await fetchFreshExchange(wex, exchangeBaseUrl);
+  if (creationInfo) {
+    await fetchFreshExchange(wex, creationInfo.canonExchange);
+  }
+
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
     withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
@@ -2676,10 +2776,7 @@ export async function 
internalPrepareCreateWithdrawalGroup(
   return {
     withdrawalGroup,
     transactionId,
-    creationInfo: {
-      canonExchange: exchangeBaseUrl,
-      amount,
-    },
+    creationInfo,
   };
 }
 
@@ -2703,13 +2800,6 @@ export async function 
internalPerformCreateWithdrawalGroup(
   prep: PrepareCreateWithdrawalGroupResult,
 ): Promise<PerformCreateWithdrawalGroupResult> {
   const { withdrawalGroup } = prep;
-  if (!prep.creationInfo) {
-    return {
-      withdrawalGroup,
-      transitionInfo: undefined,
-      exchangeNotif: undefined,
-    };
-  }
   const existingWg = await tx.withdrawalGroups.get(
     withdrawalGroup.withdrawalGroupId,
   );
@@ -2726,7 +2816,14 @@ export async function 
internalPerformCreateWithdrawalGroup(
     reservePriv: withdrawalGroup.reservePriv,
   });
 
-  const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
+  if (!prep.creationInfo) {
+    return {
+      withdrawalGroup,
+      transitionInfo: undefined,
+      exchangeNotif: undefined,
+    };
+  }
+  const exchange = await tx.exchanges.get(prep.creationInfo.canonExchange);
   if (exchange) {
     exchange.lastWithdrawal = 
timestampPreciseToDb(TalerPreciseTimestamp.now());
     await tx.exchanges.put(exchange);
@@ -2745,7 +2842,7 @@ export async function 
internalPerformCreateWithdrawalGroup(
   const exchangeUsedRes = await markExchangeUsed(
     wex,
     tx,
-    prep.withdrawalGroup.exchangeBaseUrl,
+    prep.creationInfo.canonExchange,
   );
 
   const ctx = new WithdrawTransactionContext(
@@ -2774,8 +2871,8 @@ export async function internalCreateWithdrawalGroup(
   wex: WalletExecutionContext,
   args: {
     reserveStatus: WithdrawalGroupStatus;
-    amount: AmountJson;
-    exchangeBaseUrl: string;
+    amount?: AmountJson;
+    exchangeBaseUrl?: string;
     forcedWithdrawalGroupId?: string;
     forcedDenomSel?: ForcedDenomSel;
     reserveKeyPair?: EddsaKeypair;
@@ -2820,9 +2917,6 @@ export async function prepareBankIntegratedWithdrawal(
   wex: WalletExecutionContext,
   req: {
     talerWithdrawUri: string;
-    selectedExchange: string;
-    forcedDenomSel?: ForcedDenomSel;
-    restrictAge?: number;
   },
 ): Promise<PrepareBankIntegratedWithdrawalResponse> {
   const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
@@ -2850,44 +2944,23 @@ export async function prepareBankIntegratedWithdrawal(
     };
   }
 
-  const selectedExchange = req.selectedExchange;
-  const exchange = await fetchFreshExchange(wex, selectedExchange);
-
-  const withdrawInfo = await getBankWithdrawalInfo(
-    wex.http,
-    req.talerWithdrawUri,
-  );
-  const exchangePaytoUri = await getExchangePaytoUri(
-    wex,
-    selectedExchange,
-    withdrawInfo.wireTypes,
-  );
-
-  const withdrawalAccountList = await fetchWithdrawalAccountInfo(
-    wex,
-    {
-      exchange,
-      instructedAmount: withdrawInfo.amount,
-    },
-    wex.cancellationToken,
-  );
-
+  /**
+   * Withdrawal group without exchange and amount
+   * this is an special case when the user haven't yet
+   * choose. We are still tracking this object since the state
+   * can change from the bank side or another wallet with the
+   * same URI
+   */
   const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
-    amount: withdrawInfo.amount,
-    exchangeBaseUrl: req.selectedExchange,
     wgInfo: {
       withdrawalType: WithdrawalRecordType.BankIntegrated,
-      exchangeCreditAccounts: withdrawalAccountList,
       bankInfo: {
-        exchangePaytoUri,
         talerWithdrawUri: req.talerWithdrawUri,
-        confirmUrl: withdrawInfo.confirmTransferUrl,
+        confirmUrl: undefined,
         timestampBankConfirmed: undefined,
         timestampReserveInfoPosted: undefined,
       },
     },
-    restrictAge: req.restrictAge,
-    forcedDenomSel: req.forcedDenomSel,
     reserveStatus: WithdrawalGroupStatus.DialogProposed,
   });
 
@@ -2904,9 +2977,9 @@ export async function prepareBankIntegratedWithdrawal(
 
 export async function confirmWithdrawal(
   wex: WalletExecutionContext,
-  transactionId: string,
+  req: ConfirmWithdrawalRequest,
 ): Promise<void> {
-  const parsedTx = parseTransactionIdentifier(transactionId);
+  const parsedTx = parseTransactionIdentifier(req.transactionId);
   if (parsedTx?.tag !== TransactionType.Withdrawal) {
     throw Error("invalid withdrawal transaction ID");
   }
@@ -2921,16 +2994,70 @@ export async function confirmWithdrawal(
     throw Error("withdrawal group not found");
   }
 
+  if (
+    withdrawalGroup.wgInfo.withdrawalType !==
+    WithdrawalRecordType.BankIntegrated
+  ) {
+    throw Error("not a bank integrated withdrawal");
+  }
+
+  const selectedExchange = req.exchangeBaseUrl;
+  const exchange = await fetchFreshExchange(wex, selectedExchange);
+
+  const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri;
+
+  const withdrawInfo = await getBankWithdrawalInfo(wex.http, talerWithdrawUri);
+  const exchangePaytoUri = await getExchangePaytoUri(
+    wex,
+    selectedExchange,
+    withdrawInfo.wireTypes,
+  );
+
+  const withdrawalAccountList = await fetchWithdrawalAccountInfo(
+    wex,
+    {
+      exchange,
+      instructedAmount: withdrawInfo.amount,
+    },
+    wex.cancellationToken,
+  );
+
   const ctx = new WithdrawTransactionContext(
     wex,
     withdrawalGroup.withdrawalGroupId,
   );
+  const initalDenoms = await getInitialDenomsSelection(
+    wex,
+    req.exchangeBaseUrl,
+    Amounts.parseOrThrow(req.amount),
+    req.forcedDenomSel,
+  );
+  
   ctx.transition({}, async (rec) => {
     if (!rec) {
       return TransitionResult.stay();
     }
     switch (rec.status) {
       case WithdrawalGroupStatus.DialogProposed: {
+        rec.exchangeBaseUrl = req.exchangeBaseUrl;
+        rec.instructedAmount = req.amount;
+        rec.denomsSel = initalDenoms;
+        rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost;
+        rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue;
+        rec.restrictAge = req.restrictAge;
+
+        rec.wgInfo = {
+          withdrawalType: WithdrawalRecordType.BankIntegrated,
+          exchangeCreditAccounts: withdrawalAccountList,
+          bankInfo: {
+            exchangePaytoUri,
+            talerWithdrawUri,
+            confirmUrl: withdrawInfo.confirmTransferUrl,
+            timestampBankConfirmed: undefined,
+            timestampReserveInfoPosted: undefined,
+          },
+        };
+
         rec.status = WithdrawalGroupStatus.PendingRegisteringBank;
         return TransitionResult.transition(rec);
       }

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