commit 9dd937f3a0b5e3bb87c669d8e9dda757b2982c17
parent c6f5d2f5b0bebe6e2b762e2f83f11f29a1ca09ed
Author: Florian Dold <florian@dold.me>
Date: Mon, 8 Jul 2024 22:01:13 +0200
wallet-core: refactor transaction details / new transactionsMeta store
Diffstat:
5 files changed, 529 insertions(+), 659 deletions(-)
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
@@ -57,7 +57,6 @@ import {
TalerPreciseTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
- Transaction,
TransactionIdStr,
UnblindedSignature,
WireInfo,
@@ -155,7 +154,7 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
* backwards-compatible way or object stores and indices
* are added.
*/
-export const WALLET_DB_MINOR_VERSION = 12;
+export const WALLET_DB_MINOR_VERSION = 13;
declare const symDbProtocolTimestamp: unique symbol;
@@ -2335,13 +2334,26 @@ export interface GlobalCurrencyExchangeRecord {
}
/**
- * Primary key: transactionItem.transactionId
+ * Metadata for a transaction.
+ * This object store is effectively a materialzed view of transactions gathered
+ * from various other object stores.
+ *
+ * Primary key: transactionId
*/
-export interface TransactionRecord {
+export interface TransactionMetaRecord {
/**
- * Transaction item returned to the client.
+ * Transaction identifier.
+ * Also determines the type of the transaction.
*/
- transactionItem: Transaction;
+ transactionId: string;
+
+ timestamp: DbPreciseTimestamp;
+
+ /**
+ * Status of the transaction, matches the status enum of the
+ * transaction of the type determined by the transaction ID.
+ */
+ status: number;
/**
* Exchanges involved in the transaction.
@@ -2410,19 +2422,25 @@ export const WalletStoresV1 = {
}),
},
}),
- transactions: describeStoreV2({
- recordCodec: passthroughCodec<TransactionRecord>(),
- storeName: "transactions",
- keyPath: "transactionItem.transactionId",
- versionAdded: 7,
+ transactionsMeta: describeStoreV2({
+ recordCodec: passthroughCodec<TransactionMetaRecord>(),
+ storeName: "transactionsMeta",
+ keyPath: "transactionId",
+ versionAdded: 13,
indexes: {
byCurrency: describeIndex("byCurrency", "currency", {
- versionAdded: 7,
+ versionAdded: 13,
}),
byExchange: describeIndex("byExchange", "exchanges", {
- versionAdded: 7,
+ versionAdded: 13,
multiEntry: true,
}),
+ byTimestamp: describeIndex("byTimestamp", "timestamp", {
+ versionAdded: 13,
+ }),
+ byStatus: describeIndex("byStatus", "status", {
+ versionAdded: 13,
+ }),
},
}),
currencyInfo: describeStoreV2({
@@ -2830,6 +2848,22 @@ export const WalletStoresV1 = {
}),
{},
),
+ // Obsolete store, not used anymore
+ _obsolete_transactions: describeStoreV2({
+ recordCodec: passthroughCodec<unknown>(),
+ storeName: "transactions",
+ keyPath: "transactionItem.transactionId",
+ versionAdded: 7,
+ indexes: {
+ byCurrency: describeIndex("byCurrency", "currency", {
+ versionAdded: 7,
+ }),
+ byExchange: describeIndex("byExchange", "exchanges", {
+ versionAdded: 7,
+ multiEntry: true,
+ }),
+ },
+ }),
};
export type WalletDbStoresArr = Array<StoreNames<typeof WalletStoresV1>>;
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -2506,7 +2506,7 @@ async function purgeExchange(
[
"exchanges",
"exchangeDetails",
- "transactions",
+ "transactionsMeta",
"coinAvailability",
"coins",
"denominations",
@@ -2590,7 +2590,7 @@ export async function deleteExchange(
storeNames: [
"exchanges",
"exchangeDetails",
- "transactions",
+ "transactionsMeta",
"coinAvailability",
"coins",
"denominations",
diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts
@@ -130,7 +130,7 @@ async function updateRefreshTransaction(
tx: WalletDbReadWriteTransaction<
[
"refreshGroups",
- "transactions",
+ "transactionsMeta",
"operationRetries",
"exchanges",
"exchangeDetails",
@@ -167,7 +167,7 @@ export class RefreshTransactionContext implements TransactionContext {
tx: WalletDbReadWriteTransaction<
[
"refreshGroups",
- "transactions",
+ "transactionsMeta",
"operationRetries",
"exchanges",
"exchangeDetails",
@@ -178,7 +178,7 @@ export class RefreshTransactionContext implements TransactionContext {
): Promise<TransitionInfo | undefined> {
const baseStores = [
"refreshGroups" as const,
- "transactions" as const,
+ "transactionsMeta" as const,
"operationRetries" as const,
"exchanges" as const,
"exchangeDetails" as const,
diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts
@@ -93,7 +93,6 @@ import {
computeDenomLossTransactionStatus,
DenomLossTransactionContext,
ExchangeWireDetails,
- getExchangeWireDetailsInTx,
} from "./exchanges.js";
import {
computePayMerchantTransactionActions,
@@ -224,55 +223,17 @@ export async function getTransactionById(
switch (parsedTx.tag) {
case TransactionType.InternalWithdrawal:
case TransactionType.Withdrawal: {
- const withdrawalGroupId = parsedTx.withdrawalGroupId;
- return await wex.db.runReadWriteTx(
- {
- storeNames: [
- "withdrawalGroups",
- "exchangeDetails",
- "exchanges",
- "operationRetries",
- ],
- },
- async (tx) => {
- const withdrawalGroupRecord =
- await tx.withdrawalGroups.get(withdrawalGroupId);
-
- if (!withdrawalGroupRecord) throw Error("not found");
-
- const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
- const ort = await tx.operationRetries.get(opId);
-
- const exchangeDetails =
- withdrawalGroupRecord.exchangeBaseUrl === undefined
- ? undefined
- : await getExchangeWireDetailsInTx(
- tx,
- withdrawalGroupRecord.exchangeBaseUrl,
- );
- // if (!exchangeDetails) throw Error("not exchange details");
-
- if (
- withdrawalGroupRecord.wgInfo.withdrawalType ===
- WithdrawalRecordType.BankIntegrated
- ) {
- return buildTransactionForBankIntegratedWithdraw(
- withdrawalGroupRecord,
- exchangeDetails,
- ort,
- );
- }
- checkDbInvariant(
- exchangeDetails !== undefined,
- "manual withdrawal without exchange",
- );
- return buildTransactionForManualWithdraw(
- withdrawalGroupRecord,
- exchangeDetails,
- ort,
- );
- },
+ const ctx = new WithdrawTransactionContext(
+ wex,
+ parsedTx.withdrawalGroupId,
);
+ const res = await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
+ return await ctx.lookupFullTransaction(tx);
+ });
+ if (!res) {
+ throw Error("not found");
+ }
+ return res;
}
case TransactionType.DenomLoss: {
@@ -731,61 +692,6 @@ function buildTransactionForPeerPushCredit(
};
}
-function buildTransactionForBankIntegratedWithdraw(
- wg: WithdrawalGroupRecord,
- exchangeDetails: ExchangeWireDetails | undefined,
- ort?: OperationRetryRecord,
-): TransactionWithdrawal {
- if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
- throw Error("");
- }
- const instructedCurrency =
- wg.instructedAmount === undefined
- ? undefined
- : Amounts.currencyOf(wg.instructedAmount);
- const currency = wg.wgInfo.bankInfo.currency ?? instructedCurrency;
- checkDbInvariant(
- currency !== undefined,
- "wg uninitialized (missing currency)",
- );
- const txState = computeWithdrawalTransactionStatus(wg);
-
- const zero = Amounts.stringify(Amounts.zeroOfCurrency(currency));
- return {
- type: TransactionType.Withdrawal,
- txState,
- txActions: computeWithdrawalTransactionActions(wg),
- exchangeBaseUrl: wg.exchangeBaseUrl,
- amountEffective:
- isUnsuccessfulTransaction(txState) || !wg.denomsSel
- ? zero
- : Amounts.stringify(wg.denomsSel.totalCoinValue),
- amountRaw: !wg.instructedAmount
- ? zero
- : Amounts.stringify(wg.instructedAmount),
- withdrawalDetails: {
- type: WithdrawalType.TalerBankIntegrationApi,
- confirmed: wg.wgInfo.bankInfo.timestampBankConfirmed ? true : false,
- exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
- reservePub: wg.reservePub,
- bankConfirmationUrl: wg.wgInfo.bankInfo.externalConfirmation
- ? undefined
- : wg.wgInfo.bankInfo.confirmUrl,
- externalConfirmation: wg.wgInfo.bankInfo.externalConfirmation,
- reserveIsReady:
- wg.status === WithdrawalGroupStatus.Done ||
- wg.status === WithdrawalGroupStatus.PendingReady,
- },
- kycUrl: wg.kycUrl,
- timestamp: timestampPreciseFromDb(wg.timestampStart),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Withdrawal,
- withdrawalGroupId: wg.withdrawalGroupId,
- }),
- ...(ort?.lastError ? { error: ort.lastError } : {}),
- };
-}
-
export function isUnsuccessfulTransaction(state: TransactionState): boolean {
return (
state.major === TransactionMajorState.Aborted ||
@@ -796,56 +702,6 @@ export function isUnsuccessfulTransaction(state: TransactionState): boolean {
);
}
-function buildTransactionForManualWithdraw(
- wg: WithdrawalGroupRecord,
- exchangeDetails: ExchangeWireDetails,
- ort?: OperationRetryRecord,
-): TransactionWithdrawal {
- if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual)
- throw Error("");
-
- const plainPaytoUris =
- exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
-
- checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
- checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
- checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
- const exchangePaytoUris = augmentPaytoUrisForWithdrawal(
- plainPaytoUris,
- wg.reservePub,
- wg.instructedAmount,
- );
-
- const txState = computeWithdrawalTransactionStatus(wg);
-
- return {
- type: TransactionType.Withdrawal,
- txState,
- txActions: computeWithdrawalTransactionActions(wg),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
- : Amounts.stringify(wg.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wg.instructedAmount),
- withdrawalDetails: {
- type: WithdrawalType.ManualTransfer,
- reservePub: wg.reservePub,
- exchangePaytoUris,
- exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
- reserveIsReady:
- wg.status === WithdrawalGroupStatus.Done ||
- wg.status === WithdrawalGroupStatus.PendingReady,
- },
- kycUrl: wg.kycUrl,
- exchangeBaseUrl: wg.exchangeBaseUrl,
- timestamp: timestampPreciseFromDb(wg.timestampStart),
- transactionId: constructTransactionIdentifier({
- tag: TransactionType.Withdrawal,
- withdrawalGroupId: wg.withdrawalGroupId,
- }),
- ...(ort?.lastError ? { error: ort.lastError } : {}),
- };
-}
-
function buildTransactionForRefund(
refundRecord: RefundGroupRecord,
maybeContractData: WalletContractData | undefined,
@@ -1103,56 +959,24 @@ export async function getWithdrawalTransactionByUri(
wex: WalletExecutionContext,
request: WithdrawalTransactionByURIRequest,
): Promise<TransactionWithdrawal | undefined> {
- return await wex.db.runReadWriteTx(
- {
- storeNames: [
- "withdrawalGroups",
- "exchangeDetails",
- "exchanges",
- "operationRetries",
- ],
- },
- async (tx) => {
- const withdrawalGroupRecord =
- await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
- request.talerWithdrawUri,
- );
-
- if (!withdrawalGroupRecord) {
- return undefined;
- }
- if (withdrawalGroupRecord.exchangeBaseUrl === undefined) {
- // prepared and unconfirmed withdrawals are hidden
- return undefined;
- }
-
- const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
- const ort = await tx.operationRetries.get(opId);
-
- const exchangeDetails = await getExchangeWireDetailsInTx(
- tx,
- withdrawalGroupRecord.exchangeBaseUrl,
- );
- if (!exchangeDetails) throw Error("not exchange details");
-
- if (
- withdrawalGroupRecord.wgInfo.withdrawalType ===
- WithdrawalRecordType.BankIntegrated
- ) {
- return buildTransactionForBankIntegratedWithdraw(
- withdrawalGroupRecord,
- exchangeDetails,
- ort,
- );
- }
-
- return buildTransactionForManualWithdraw(
- withdrawalGroupRecord,
- exchangeDetails,
- ort,
+ return await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
+ const withdrawalGroupRecord =
+ await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
+ request.talerWithdrawUri,
);
- },
- );
+ if (!withdrawalGroupRecord) {
+ return undefined;
+ }
+ const ctx = new WithdrawTransactionContext(
+ wex,
+ withdrawalGroupRecord.withdrawalGroupId,
+ );
+ const dbTxn = await ctx.lookupFullTransaction(tx);
+ if (!dbTxn || dbTxn.type !== TransactionType.Withdrawal) {
+ return undefined;
+ }
+ return dbTxn;
+ });
}
/**
@@ -1169,399 +993,327 @@ export async function getTransactions(
filter.onlyState = transactionsRequest.filterByState;
}
- await wex.db.runReadOnlyTx(
- {
- storeNames: [
- "coins",
- "denominations",
- "depositGroups",
- "exchangeDetails",
- "exchanges",
- "operationRetries",
- "peerPullDebit",
- "peerPushDebit",
- "peerPushCredit",
- "peerPullCredit",
- "planchets",
- "purchases",
- "contractTerms",
- "recoupGroups",
- "rewards",
- "tombstones",
- "withdrawalGroups",
- "refreshGroups",
- "refundGroups",
- "denomLossEvents",
- ],
- },
- async (tx) => {
- await iterRecordsForPeerPushDebit(tx, filter, async (pi) => {
- const amount = Amounts.parseOrThrow(pi.amount);
- const exchangesInTx = [pi.exchangeBaseUrl];
- if (
- shouldSkipCurrency(
- transactionsRequest,
- amount.currency,
- exchangesInTx,
- )
- ) {
- return;
- }
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
- }
- const ct = await tx.contractTerms.get(pi.contractTermsHash);
- checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
- transactions.push(
- buildTransactionForPushPaymentDebit(pi, ct.contractTermsRaw),
- );
- });
+ await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
+ await iterRecordsForPeerPushDebit(tx, filter, async (pi) => {
+ const amount = Amounts.parseOrThrow(pi.amount);
+ const exchangesInTx = [pi.exchangeBaseUrl];
+ if (
+ shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
+ ) {
+ return;
+ }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ const ct = await tx.contractTerms.get(pi.contractTermsHash);
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
+ transactions.push(
+ buildTransactionForPushPaymentDebit(pi, ct.contractTermsRaw),
+ );
+ });
- await iterRecordsForPeerPullDebit(tx, filter, async (pi) => {
- const amount = Amounts.parseOrThrow(pi.amount);
- const exchangesInTx = [pi.exchangeBaseUrl];
- if (
- shouldSkipCurrency(
- transactionsRequest,
- amount.currency,
- exchangesInTx,
- )
- ) {
- return;
- }
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
- }
- if (
- pi.status !== PeerPullDebitRecordStatus.PendingDeposit &&
- pi.status !== PeerPullDebitRecordStatus.Done
- ) {
- // FIXME: Why?!
- return;
- }
+ await iterRecordsForPeerPullDebit(tx, filter, async (pi) => {
+ const amount = Amounts.parseOrThrow(pi.amount);
+ const exchangesInTx = [pi.exchangeBaseUrl];
+ if (
+ shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
+ ) {
+ return;
+ }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ if (
+ pi.status !== PeerPullDebitRecordStatus.PendingDeposit &&
+ pi.status !== PeerPullDebitRecordStatus.Done
+ ) {
+ // FIXME: Why?!
+ return;
+ }
- const contractTermsRec = await tx.contractTerms.get(
- pi.contractTermsHash,
- );
- if (!contractTermsRec) {
- return;
- }
+ const contractTermsRec = await tx.contractTerms.get(pi.contractTermsHash);
+ if (!contractTermsRec) {
+ return;
+ }
- transactions.push(
- buildTransactionForPullPaymentDebit(
- pi,
- contractTermsRec.contractTermsRaw,
- ),
- );
- });
+ transactions.push(
+ buildTransactionForPullPaymentDebit(
+ pi,
+ contractTermsRec.contractTermsRaw,
+ ),
+ );
+ });
- await iterRecordsForPeerPushCredit(tx, filter, async (pi) => {
- if (!pi.currency) {
- // Legacy transaction
- return;
- }
- const exchangesInTx = [pi.exchangeBaseUrl];
- if (
- shouldSkipCurrency(transactionsRequest, pi.currency, exchangesInTx)
- ) {
- return;
- }
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
- }
- if (pi.status === PeerPushCreditStatus.DialogProposed) {
- // We don't report proposed push credit transactions, user needs
- // to scan URI again and confirm to see it.
- return;
- }
- const ct = await tx.contractTerms.get(pi.contractTermsHash);
- let wg: WithdrawalGroupRecord | undefined = undefined;
- let wgOrt: OperationRetryRecord | undefined = undefined;
- if (pi.withdrawalGroupId) {
- wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
- if (wg) {
- const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
- wgOrt = await tx.operationRetries.get(withdrawalOpId);
- }
+ await iterRecordsForPeerPushCredit(tx, filter, async (pi) => {
+ if (!pi.currency) {
+ // Legacy transaction
+ return;
+ }
+ const exchangesInTx = [pi.exchangeBaseUrl];
+ if (shouldSkipCurrency(transactionsRequest, pi.currency, exchangesInTx)) {
+ return;
+ }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ if (pi.status === PeerPushCreditStatus.DialogProposed) {
+ // We don't report proposed push credit transactions, user needs
+ // to scan URI again and confirm to see it.
+ return;
+ }
+ const ct = await tx.contractTerms.get(pi.contractTermsHash);
+ let wg: WithdrawalGroupRecord | undefined = undefined;
+ let wgOrt: OperationRetryRecord | undefined = undefined;
+ if (pi.withdrawalGroupId) {
+ wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
+ if (wg) {
+ const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
+ wgOrt = await tx.operationRetries.get(withdrawalOpId);
}
- const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pi);
- const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
-
- checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
- transactions.push(
- buildTransactionForPeerPushCredit(
- pi,
- pushIncOrt,
- ct.contractTermsRaw,
- wg,
- wgOrt,
- ),
- );
- });
+ }
+ const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pi);
+ const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
+
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
+ transactions.push(
+ buildTransactionForPeerPushCredit(
+ pi,
+ pushIncOrt,
+ ct.contractTermsRaw,
+ wg,
+ wgOrt,
+ ),
+ );
+ });
- await iterRecordsForPeerPullCredit(tx, filter, async (pi) => {
- const currency = Amounts.currencyOf(pi.amount);
- const exchangesInTx = [pi.exchangeBaseUrl];
- if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) {
- return;
- }
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
- }
- const ct = await tx.contractTerms.get(pi.contractTermsHash);
- let wg: WithdrawalGroupRecord | undefined = undefined;
- let wgOrt: OperationRetryRecord | undefined = undefined;
- if (pi.withdrawalGroupId) {
- wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
- if (wg) {
- const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
- wgOrt = await tx.operationRetries.get(withdrawalOpId);
- }
+ await iterRecordsForPeerPullCredit(tx, filter, async (pi) => {
+ const currency = Amounts.currencyOf(pi.amount);
+ const exchangesInTx = [pi.exchangeBaseUrl];
+ if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) {
+ return;
+ }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
+ const ct = await tx.contractTerms.get(pi.contractTermsHash);
+ let wg: WithdrawalGroupRecord | undefined = undefined;
+ let wgOrt: OperationRetryRecord | undefined = undefined;
+ if (pi.withdrawalGroupId) {
+ wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
+ if (wg) {
+ const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
+ wgOrt = await tx.operationRetries.get(withdrawalOpId);
}
- const pushIncOpId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
- const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
-
- checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
- transactions.push(
- buildTransactionForPeerPullCredit(
- pi,
- pushIncOrt,
- ct.contractTermsRaw,
- wg,
- wgOrt,
- ),
- );
- });
+ }
+ const pushIncOpId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
+ const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
+
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
+ transactions.push(
+ buildTransactionForPeerPullCredit(
+ pi,
+ pushIncOrt,
+ ct.contractTermsRaw,
+ wg,
+ wgOrt,
+ ),
+ );
+ });
- await iterRecordsForRefund(tx, filter, async (refundGroup) => {
- const currency = Amounts.currencyOf(refundGroup.amountRaw);
+ await iterRecordsForRefund(tx, filter, async (refundGroup) => {
+ const currency = Amounts.currencyOf(refundGroup.amountRaw);
- const exchangesInTx: string[] = [];
- const p = await tx.purchases.get(refundGroup.proposalId);
- if (!p || !p.payInfo || !p.payInfo.payCoinSelection) {
- //refund with no payment
- return;
- }
+ const exchangesInTx: string[] = [];
+ const p = await tx.purchases.get(refundGroup.proposalId);
+ if (!p || !p.payInfo || !p.payInfo.payCoinSelection) {
+ //refund with no payment
+ return;
+ }
- // FIXME: This is very slow, should become obsolete with materialized transactions.
- for (const cp of p.payInfo.payCoinSelection.coinPubs) {
- const c = await tx.coins.get(cp);
- if (c?.exchangeBaseUrl) {
- exchangesInTx.push(c.exchangeBaseUrl);
- }
+ // FIXME: This is very slow, should become obsolete with materialized transactions.
+ for (const cp of p.payInfo.payCoinSelection.coinPubs) {
+ const c = await tx.coins.get(cp);
+ if (c?.exchangeBaseUrl) {
+ exchangesInTx.push(c.exchangeBaseUrl);
}
+ }
- if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) {
- return;
- }
- const contractData = await lookupMaybeContractData(
- tx,
- refundGroup.proposalId,
- );
- transactions.push(buildTransactionForRefund(refundGroup, contractData));
- });
+ if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) {
+ return;
+ }
+ const contractData = await lookupMaybeContractData(
+ tx,
+ refundGroup.proposalId,
+ );
+ transactions.push(buildTransactionForRefund(refundGroup, contractData));
+ });
- await iterRecordsForRefresh(tx, filter, async (rg) => {
- const exchangesInTx = rg.infoPerExchange
- ? Object.keys(rg.infoPerExchange)
- : [];
- if (
- shouldSkipCurrency(transactionsRequest, rg.currency, exchangesInTx)
- ) {
- return;
- }
- let required = false;
- const opId = TaskIdentifiers.forRefresh(rg);
- if (transactionsRequest?.includeRefreshes) {
+ await iterRecordsForRefresh(tx, filter, async (rg) => {
+ const exchangesInTx = rg.infoPerExchange
+ ? Object.keys(rg.infoPerExchange)
+ : [];
+ if (shouldSkipCurrency(transactionsRequest, rg.currency, exchangesInTx)) {
+ return;
+ }
+ let required = false;
+ const opId = TaskIdentifiers.forRefresh(rg);
+ if (transactionsRequest?.includeRefreshes) {
+ required = true;
+ } else if (rg.operationStatus !== RefreshOperationStatus.Finished) {
+ const ort = await tx.operationRetries.get(opId);
+ if (ort) {
required = true;
- } else if (rg.operationStatus !== RefreshOperationStatus.Finished) {
- const ort = await tx.operationRetries.get(opId);
- if (ort) {
- required = true;
- }
- }
- if (required) {
- const ort = await tx.operationRetries.get(opId);
- transactions.push(buildTransactionForRefresh(rg, ort));
- }
- });
-
- 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(
- transactionsRequest,
- Amounts.currencyOf(wsr.rawWithdrawalAmount),
- exchangesInTx,
- )
- ) {
- return;
- }
-
- if (shouldSkipSearch(transactionsRequest, [])) {
- return;
}
-
- const opId = TaskIdentifiers.forWithdrawal(wsr);
+ }
+ if (required) {
const ort = await tx.operationRetries.get(opId);
+ transactions.push(buildTransactionForRefresh(rg, ort));
+ }
+ });
- switch (wsr.wgInfo.withdrawalType) {
- case WithdrawalRecordType.PeerPullCredit:
- // Will be reported by the corresponding p2p transaction.
- // FIXME: If this is an orphan withdrawal, still report it as a withdrawal!
- // FIXME: Still report if requested with verbose option?
- return;
- case WithdrawalRecordType.PeerPushCredit:
- // Will be reported by the corresponding p2p transaction.
- // FIXME: If this is an orphan withdrawal, still report it as a withdrawal!
- // FIXME: Still report if requested with verbose option?
- return;
- case WithdrawalRecordType.BankIntegrated: {
- const exchangeDetails = await getExchangeWireDetailsInTx(
- tx,
- wsr.exchangeBaseUrl,
- );
- if (!exchangeDetails) {
- // FIXME: report somehow
- return;
- }
+ 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(
+ transactionsRequest,
+ Amounts.currencyOf(wsr.rawWithdrawalAmount),
+ exchangesInTx,
+ )
+ ) {
+ return;
+ }
- transactions.push(
- buildTransactionForBankIntegratedWithdraw(
- wsr,
- exchangeDetails,
- ort,
- ),
- );
- return;
- }
+ if (shouldSkipSearch(transactionsRequest, [])) {
+ return;
+ }
- case WithdrawalRecordType.BankManual: {
- const exchangeDetails = await getExchangeWireDetailsInTx(
- tx,
- wsr.exchangeBaseUrl,
- );
- if (!exchangeDetails) {
- // FIXME: report somehow
- return;
- }
- transactions.push(
- buildTransactionForManualWithdraw(wsr, exchangeDetails, ort),
- );
+ switch (wsr.wgInfo.withdrawalType) {
+ case WithdrawalRecordType.PeerPullCredit:
+ // Will be reported by the corresponding p2p transaction.
+ // FIXME: If this is an orphan withdrawal, still report it as a withdrawal!
+ // FIXME: Still report if requested with verbose option?
+ return;
+ case WithdrawalRecordType.PeerPushCredit:
+ // Will be reported by the corresponding p2p transaction.
+ // FIXME: If this is an orphan withdrawal, still report it as a withdrawal!
+ // FIXME: Still report if requested with verbose option?
+ return;
+ case WithdrawalRecordType.BankIntegrated:
+ case WithdrawalRecordType.BankManual: {
+ const ctx = new WithdrawTransactionContext(
+ wex,
+ wsr.withdrawalGroupId,
+ );
+ const dbTxn = await ctx.lookupFullTransaction(tx);
+ if (!dbTxn) {
return;
}
- case WithdrawalRecordType.Recoup:
- // FIXME: Do we also report a transaction here?
- return;
- }
- });
-
- await iterRecordsForDenomLoss(tx, filter, async (rec) => {
- const amount = Amounts.parseOrThrow(rec.amount);
- const exchangesInTx = [rec.exchangeBaseUrl];
- if (
- shouldSkipCurrency(
- transactionsRequest,
- amount.currency,
- exchangesInTx,
- )
- ) {
+ transactions.push(dbTxn);
return;
}
- transactions.push(buildTransactionForDenomLoss(rec));
- });
-
- await iterRecordsForDeposit(tx, filter, async (dg) => {
- const amount = Amounts.parseOrThrow(dg.amount);
- const exchangesInTx = dg.infoPerExchange
- ? Object.keys(dg.infoPerExchange)
- : [];
- if (
- shouldSkipCurrency(
- transactionsRequest,
- amount.currency,
- exchangesInTx,
- )
- ) {
+ case WithdrawalRecordType.Recoup:
+ // FIXME: Do we also report a transaction here?
return;
- }
- const opId = TaskIdentifiers.forDeposit(dg);
- const retryRecord = await tx.operationRetries.get(opId);
+ }
+ });
- transactions.push(buildTransactionForDeposit(dg, retryRecord));
- });
+ await iterRecordsForDenomLoss(tx, filter, async (rec) => {
+ const amount = Amounts.parseOrThrow(rec.amount);
+ const exchangesInTx = [rec.exchangeBaseUrl];
+ if (
+ shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
+ ) {
+ return;
+ }
+ transactions.push(buildTransactionForDenomLoss(rec));
+ });
- await iterRecordsForPurchase(tx, filter, async (purchase) => {
- const download = purchase.download;
- if (!download) {
- return;
- }
- if (!purchase.payInfo) {
- return;
- }
+ await iterRecordsForDeposit(tx, filter, async (dg) => {
+ const amount = Amounts.parseOrThrow(dg.amount);
+ const exchangesInTx = dg.infoPerExchange
+ ? Object.keys(dg.infoPerExchange)
+ : [];
+ if (
+ shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
+ ) {
+ return;
+ }
+ const opId = TaskIdentifiers.forDeposit(dg);
+ const retryRecord = await tx.operationRetries.get(opId);
- const exchangesInTx: string[] = [];
- for (const cp of purchase.payInfo.payCoinSelection?.coinPubs ?? []) {
- const c = await tx.coins.get(cp);
- if (c?.exchangeBaseUrl) {
- exchangesInTx.push(c.exchangeBaseUrl);
- }
- }
+ transactions.push(buildTransactionForDeposit(dg, retryRecord));
+ });
- if (
- shouldSkipCurrency(
- transactionsRequest,
- download.currency,
- exchangesInTx,
- )
- ) {
- return;
- }
- const contractTermsRecord = await tx.contractTerms.get(
- download.contractTermsHash,
- );
- if (!contractTermsRecord) {
- return;
- }
- if (
- shouldSkipSearch(transactionsRequest, [
- contractTermsRecord?.contractTermsRaw?.summary || "",
- ])
- ) {
- return;
+ await iterRecordsForPurchase(tx, filter, async (purchase) => {
+ const download = purchase.download;
+ if (!download) {
+ return;
+ }
+ if (!purchase.payInfo) {
+ return;
+ }
+
+ const exchangesInTx: string[] = [];
+ for (const cp of purchase.payInfo.payCoinSelection?.coinPubs ?? []) {
+ const c = await tx.coins.get(cp);
+ if (c?.exchangeBaseUrl) {
+ exchangesInTx.push(c.exchangeBaseUrl);
}
+ }
- const contractData = extractContractData(
- contractTermsRecord?.contractTermsRaw,
- download.contractTermsHash,
- download.contractTermsMerchantSig,
- );
+ if (
+ shouldSkipCurrency(
+ transactionsRequest,
+ download.currency,
+ exchangesInTx,
+ )
+ ) {
+ return;
+ }
+ const contractTermsRecord = await tx.contractTerms.get(
+ download.contractTermsHash,
+ );
+ if (!contractTermsRecord) {
+ return;
+ }
+ if (
+ shouldSkipSearch(transactionsRequest, [
+ contractTermsRecord?.contractTermsRaw?.summary || "",
+ ])
+ ) {
+ return;
+ }
- const payOpId = TaskIdentifiers.forPay(purchase);
- const payRetryRecord = await tx.operationRetries.get(payOpId);
+ const contractData = extractContractData(
+ contractTermsRecord?.contractTermsRaw,
+ download.contractTermsHash,
+ download.contractTermsMerchantSig,
+ );
- const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
- purchase.proposalId,
- );
+ const payOpId = TaskIdentifiers.forPay(purchase);
+ const payRetryRecord = await tx.operationRetries.get(payOpId);
- transactions.push(
- buildTransactionForPurchase(
- purchase,
- contractData,
- refunds,
- payRetryRecord,
- ),
- );
- });
- },
- );
+ const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
+ purchase.proposalId,
+ );
+
+ transactions.push(
+ buildTransactionForPurchase(
+ purchase,
+ contractData,
+ refunds,
+ payRetryRecord,
+ ),
+ );
+ });
+ });
// One-off checks, because of a bug where the wallet previously
// did not migrate the DB correctly and caused these amounts
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -72,6 +72,7 @@ import {
TransactionMinorState,
TransactionState,
TransactionType,
+ TransactionWithdrawal,
URL,
UnblindedSignature,
WalletNotification,
@@ -129,8 +130,10 @@ import {
DenominationRecord,
DenominationVerificationStatus,
KycPendingInfo,
+ OperationRetryRecord,
PlanchetRecord,
PlanchetStatus,
+ WalletDbAllStoresReadOnlyTransaction,
WalletDbReadOnlyTransaction,
WalletDbReadWriteTransaction,
WalletDbStoresArr,
@@ -149,6 +152,7 @@ import {
} from "./denomSelection.js";
import { isWithdrawableDenom } from "./denominations.js";
import {
+ ExchangeWireDetails,
ReadyExchangeSummary,
fetchFreshExchange,
getExchangePaytoUri,
@@ -182,7 +186,7 @@ async function updateWithdrawalTransaction(
tx: WalletDbReadWriteTransaction<
[
"withdrawalGroups",
- "transactions",
+ "transactionsMeta",
"operationRetries",
"exchanges",
"exchangeDetails",
@@ -191,12 +195,9 @@ async function updateWithdrawalTransaction(
): Promise<void> {
const wgRecord = await tx.withdrawalGroups.get(ctx.withdrawalGroupId);
if (!wgRecord) {
- await tx.transactions.delete(ctx.transactionId);
+ await tx.transactionsMeta.delete(ctx.transactionId);
return;
}
- const retryRecord = await tx.operationRetries.get(ctx.taskId);
-
- let transactionItem: Transaction;
if (
!wgRecord.instructedAmount ||
@@ -208,32 +209,6 @@ async function updateWithdrawalTransaction(
}
if (wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated) {
- const txState = computeWithdrawalTransactionStatus(wgRecord);
- transactionItem = {
- type: TransactionType.Withdrawal,
- txState,
- txActions: computeWithdrawalTransactionActions(wgRecord),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(wgRecord.instructedAmount))
- : Amounts.stringify(wgRecord.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wgRecord.instructedAmount),
- withdrawalDetails: {
- type: WithdrawalType.TalerBankIntegrationApi,
- confirmed: wgRecord.wgInfo.bankInfo.timestampBankConfirmed
- ? true
- : false,
- exchangeCreditAccountDetails: wgRecord.wgInfo.exchangeCreditAccounts,
- reservePub: wgRecord.reservePub,
- bankConfirmationUrl: wgRecord.wgInfo.bankInfo.confirmUrl,
- reserveIsReady:
- wgRecord.status === WithdrawalGroupStatus.Done ||
- wgRecord.status === WithdrawalGroupStatus.PendingReady,
- },
- kycUrl: wgRecord.kycUrl,
- exchangeBaseUrl: wgRecord.exchangeBaseUrl,
- timestamp: timestampPreciseFromDb(wgRecord.timestampStart),
- transactionId: ctx.transactionId,
- };
} else if (
wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankManual
) {
@@ -245,62 +220,127 @@ async function updateWithdrawalTransaction(
wgRecord.denomsSel !== undefined,
"manual withdrawal without denoms can't be created",
);
- const exchangeDetails = await getExchangeWireDetailsInTx(
- tx,
- wgRecord.exchangeBaseUrl,
- );
- const plainPaytoUris =
- exchangeDetails?.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
-
- const exchangePaytoUris = augmentPaytoUrisForWithdrawal(
- plainPaytoUris,
- wgRecord.reservePub,
- wgRecord.instructedAmount,
- );
-
- const txState = computeWithdrawalTransactionStatus(wgRecord);
-
- transactionItem = {
- type: TransactionType.Withdrawal,
- txState,
- txActions: computeWithdrawalTransactionActions(wgRecord),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(wgRecord.instructedAmount))
- : Amounts.stringify(wgRecord.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wgRecord.instructedAmount),
- withdrawalDetails: {
- type: WithdrawalType.ManualTransfer,
- reservePub: wgRecord.reservePub,
- exchangePaytoUris,
- exchangeCreditAccountDetails: wgRecord.wgInfo.exchangeCreditAccounts,
- reserveIsReady:
- wgRecord.status === WithdrawalGroupStatus.Done ||
- wgRecord.status === WithdrawalGroupStatus.PendingReady,
- },
- kycUrl: wgRecord.kycUrl,
- exchangeBaseUrl: wgRecord.exchangeBaseUrl,
- timestamp: timestampPreciseFromDb(wgRecord.timestampStart),
- transactionId: ctx.transactionId,
- };
} else {
// FIXME: If this is an orphaned withdrawal for a p2p transaction, we
// still might want to report the withdrawal.
return;
}
-
- if (retryRecord?.lastError) {
- transactionItem.error = retryRecord.lastError;
- }
-
- await tx.transactions.put({
+ await tx.transactionsMeta.put({
+ transactionId: ctx.transactionId,
+ status: wgRecord.status,
+ timestamp: wgRecord.timestampStart,
currency: Amounts.currencyOf(wgRecord.instructedAmount),
- transactionItem,
exchanges: [wgRecord.exchangeBaseUrl],
});
// FIXME: Handle orphaned withdrawals where the p2p or recoup tx was deleted?
}
+function buildTransactionForBankIntegratedWithdraw(
+ wg: WithdrawalGroupRecord,
+ exchangeDetails: ExchangeWireDetails | undefined,
+ ort?: OperationRetryRecord,
+): TransactionWithdrawal {
+ if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
+ throw Error("");
+ }
+ const instructedCurrency =
+ wg.instructedAmount === undefined
+ ? undefined
+ : Amounts.currencyOf(wg.instructedAmount);
+ const currency = wg.wgInfo.bankInfo.currency ?? instructedCurrency;
+ checkDbInvariant(
+ currency !== undefined,
+ "wg uninitialized (missing currency)",
+ );
+ const txState = computeWithdrawalTransactionStatus(wg);
+
+ const zero = Amounts.stringify(Amounts.zeroOfCurrency(currency));
+ return {
+ type: TransactionType.Withdrawal,
+ txState,
+ txActions: computeWithdrawalTransactionActions(wg),
+ exchangeBaseUrl: wg.exchangeBaseUrl,
+ amountEffective:
+ isUnsuccessfulTransaction(txState) || !wg.denomsSel
+ ? zero
+ : Amounts.stringify(wg.denomsSel.totalCoinValue),
+ amountRaw: !wg.instructedAmount
+ ? zero
+ : Amounts.stringify(wg.instructedAmount),
+ withdrawalDetails: {
+ type: WithdrawalType.TalerBankIntegrationApi,
+ confirmed: wg.wgInfo.bankInfo.timestampBankConfirmed ? true : false,
+ exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
+ reservePub: wg.reservePub,
+ bankConfirmationUrl: wg.wgInfo.bankInfo.externalConfirmation
+ ? undefined
+ : wg.wgInfo.bankInfo.confirmUrl,
+ externalConfirmation: wg.wgInfo.bankInfo.externalConfirmation,
+ reserveIsReady:
+ wg.status === WithdrawalGroupStatus.Done ||
+ wg.status === WithdrawalGroupStatus.PendingReady,
+ },
+ kycUrl: wg.kycUrl,
+ timestamp: timestampPreciseFromDb(wg.timestampStart),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId: wg.withdrawalGroupId,
+ }),
+ ...(ort?.lastError ? { error: ort.lastError } : {}),
+ };
+}
+
+function buildTransactionForManualWithdraw(
+ wg: WithdrawalGroupRecord,
+ exchangeDetails: ExchangeWireDetails,
+ ort?: OperationRetryRecord,
+): TransactionWithdrawal {
+ if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual)
+ throw Error("");
+
+ const plainPaytoUris =
+ exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
+
+ checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
+ const exchangePaytoUris = augmentPaytoUrisForWithdrawal(
+ plainPaytoUris,
+ wg.reservePub,
+ wg.instructedAmount,
+ );
+
+ const txState = computeWithdrawalTransactionStatus(wg);
+
+ return {
+ type: TransactionType.Withdrawal,
+ txState,
+ txActions: computeWithdrawalTransactionActions(wg),
+ amountEffective: isUnsuccessfulTransaction(txState)
+ ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
+ : Amounts.stringify(wg.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wg.instructedAmount),
+ withdrawalDetails: {
+ type: WithdrawalType.ManualTransfer,
+ reservePub: wg.reservePub,
+ exchangePaytoUris,
+ exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
+ reserveIsReady:
+ wg.status === WithdrawalGroupStatus.Done ||
+ wg.status === WithdrawalGroupStatus.PendingReady,
+ },
+ kycUrl: wg.kycUrl,
+ exchangeBaseUrl: wg.exchangeBaseUrl,
+ timestamp: timestampPreciseFromDb(wg.timestampStart),
+ transactionId: constructTransactionIdentifier({
+ tag: TransactionType.Withdrawal,
+ withdrawalGroupId: wg.withdrawalGroupId,
+ }),
+ ...(ort?.lastError ? { error: ort.lastError } : {}),
+ };
+}
+
export class WithdrawTransactionContext implements TransactionContext {
readonly transactionId: TransactionIdStr;
readonly taskId: TaskIdStr;
@@ -320,6 +360,50 @@ export class WithdrawTransactionContext implements TransactionContext {
}
/**
+ * Get the full transaction details for the transaction.
+ *
+ * Returns undefined if the transaction is in a state where we do not have a
+ * transaction item (e.g. if it was deleted).
+ */
+ async lookupFullTransaction(
+ tx: WalletDbAllStoresReadOnlyTransaction,
+ ): Promise<Transaction | undefined> {
+ const withdrawalGroupRecord = await tx.withdrawalGroups.get(
+ this.withdrawalGroupId,
+ );
+ if (!withdrawalGroupRecord) {
+ return undefined;
+ }
+ const ort = await tx.operationRetries.get(this.taskId);
+ const exchangeDetails =
+ withdrawalGroupRecord.exchangeBaseUrl === undefined
+ ? undefined
+ : await getExchangeWireDetailsInTx(
+ tx,
+ withdrawalGroupRecord.exchangeBaseUrl,
+ );
+ if (
+ withdrawalGroupRecord.wgInfo.withdrawalType ===
+ WithdrawalRecordType.BankIntegrated
+ ) {
+ return buildTransactionForBankIntegratedWithdraw(
+ withdrawalGroupRecord,
+ exchangeDetails,
+ ort,
+ );
+ }
+ checkDbInvariant(
+ exchangeDetails !== undefined,
+ "manual withdrawal without exchange",
+ );
+ return buildTransactionForManualWithdraw(
+ withdrawalGroupRecord,
+ exchangeDetails,
+ ort,
+ );
+ }
+
+ /**
* Transition a withdrawal transaction.
* Extra object stores may be accessed during the transition.
*/
@@ -330,7 +414,7 @@ export class WithdrawTransactionContext implements TransactionContext {
tx: WalletDbReadWriteTransaction<
[
"withdrawalGroups",
- "transactions",
+ "transactionsMeta",
"operationRetries",
"exchanges",
"exchangeDetails",
@@ -341,7 +425,7 @@ export class WithdrawTransactionContext implements TransactionContext {
): Promise<TransitionInfo | undefined> {
const baseStores = [
"withdrawalGroups" as const,
- "transactions" as const,
+ "transactionsMeta" as const,
"operationRetries" as const,
"exchanges" as const,
"exchangeDetails" as const,
@@ -3034,7 +3118,7 @@ export async function internalCreateWithdrawalGroup(
"reserves",
"exchanges",
"exchangeDetails",
- "transactions",
+ "transactionsMeta",
"operationRetries",
],
},