aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r--packages/taler-wallet-core/src/balance.ts64
-rw-r--r--packages/taler-wallet-core/src/db.ts16
-rw-r--r--packages/taler-wallet-core/src/transactions.ts114
-rw-r--r--packages/taler-wallet-core/src/wallet.ts37
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts323
5 files changed, 354 insertions, 200 deletions
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);
}