summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/deposits.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/deposits.ts')
-rw-r--r--packages/taler-wallet-core/src/deposits.ts566
1 files changed, 349 insertions, 217 deletions
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
index 6c04b20de..c4cd98d73 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -39,10 +39,10 @@ import {
Logger,
MerchantContractTerms,
NotificationType,
- PayCoinSelection,
PrepareDepositRequest,
PrepareDepositResponse,
RefreshReason,
+ SelectedProspectiveCoin,
TalerError,
TalerErrorCode,
TalerPreciseTimestamp,
@@ -110,7 +110,6 @@ import {
parseTransactionIdentifier,
} from "./transactions.js";
import { WalletExecutionContext, getDenomInfo } from "./wallet.js";
-import { getCandidateWithdrawalDenomsTx } from "./withdraw.js";
/**
* Logger.
@@ -140,22 +139,25 @@ export class DepositTransactionContext implements TransactionContext {
const ws = this.wex;
// FIXME: We should check first if we are in a final state
// where deletion is allowed.
- await ws.db.runReadWriteTx(["depositGroups", "tombstones"], async (tx) => {
- const tipRecord = await tx.depositGroups.get(depositGroupId);
- if (tipRecord) {
- await tx.depositGroups.delete(depositGroupId);
- await tx.tombstones.put({
- id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId,
- });
- }
- });
+ await ws.db.runReadWriteTx(
+ { storeNames: ["depositGroups", "tombstones"] },
+ async (tx) => {
+ const tipRecord = await tx.depositGroups.get(depositGroupId);
+ if (tipRecord) {
+ await tx.depositGroups.delete(depositGroupId);
+ await tx.tombstones.put({
+ id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId,
+ });
+ }
+ },
+ );
return;
}
async suspendTransaction(): Promise<void> {
const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
@@ -198,7 +200,7 @@ export class DepositTransactionContext implements TransactionContext {
async abortTransaction(): Promise<void> {
const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
@@ -236,7 +238,7 @@ export class DepositTransactionContext implements TransactionContext {
async resumeTransaction(): Promise<void> {
const { wex, depositGroupId, transactionId, taskId: retryTag } = this;
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
@@ -279,7 +281,7 @@ export class DepositTransactionContext implements TransactionContext {
async failTransaction(): Promise<void> {
const { wex, depositGroupId, transactionId, taskId } = this;
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
@@ -413,25 +415,37 @@ async function refundDepositGroup(
wex: WalletExecutionContext,
depositGroup: DepositGroupRecord,
): Promise<TaskRunResult> {
- const newTxPerCoin = [...depositGroup.statusPerCoin];
+ const statusPerCoin = depositGroup.statusPerCoin;
+ const payCoinSelection = depositGroup.payCoinSelection;
+ if (!statusPerCoin) {
+ throw Error(
+ "unable to refund deposit group without coin selection (status missing)",
+ );
+ }
+ if (!payCoinSelection) {
+ throw Error(
+ "unable to refund deposit group without coin selection (selection missing)",
+ );
+ }
+ const newTxPerCoin = [...statusPerCoin];
logger.info(`status per coin: ${j2s(depositGroup.statusPerCoin)}`);
- for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
- const st = depositGroup.statusPerCoin[i];
+ for (let i = 0; i < statusPerCoin.length; i++) {
+ const st = statusPerCoin[i];
switch (st) {
case DepositElementStatus.RefundFailed:
case DepositElementStatus.RefundSuccess:
break;
default: {
- const coinPub = depositGroup.payCoinSelection.coinPubs[i];
+ const coinPub = payCoinSelection.coinPubs[i];
const coinExchange = await wex.db.runReadOnlyTx(
- ["coins"],
+ { storeNames: ["coins"] },
async (tx) => {
const coinRecord = await tx.coins.get(coinPub);
checkDbInvariant(!!coinRecord);
return coinRecord.exchangeBaseUrl;
},
);
- const refundAmount = depositGroup.payCoinSelection.coinContributions[i];
+ const refundAmount = payCoinSelection.coinContributions[i];
// We use a constant refund transaction ID, since there can
// only be one refund.
const rtid = 1;
@@ -486,13 +500,16 @@ async function refundDepositGroup(
const currency = Amounts.currencyOf(depositGroup.totalPayCost);
const res = await wex.db.runReadWriteTx(
- [
- "depositGroups",
- "refreshGroups",
- "coins",
- "denominations",
- "coinAvailability",
- ],
+ {
+ storeNames: [
+ "depositGroups",
+ "refreshGroups",
+ "refreshSessions",
+ "coins",
+ "denominations",
+ "coinAvailability",
+ ],
+ },
async (tx) => {
const newDg = await tx.depositGroups.get(depositGroup.depositGroupId);
if (!newDg) {
@@ -502,8 +519,8 @@ async function refundDepositGroup(
const refreshCoins: CoinRefreshRequest[] = [];
for (let i = 0; i < newTxPerCoin.length; i++) {
refreshCoins.push({
- amount: depositGroup.payCoinSelection.coinContributions[i],
- coinPub: depositGroup.payCoinSelection.coinPubs[i],
+ amount: payCoinSelection.coinContributions[i],
+ coinPub: payCoinSelection.coinPubs[i],
});
}
let refreshRes: CreateRefreshGroupResult | undefined = undefined;
@@ -559,7 +576,7 @@ async function waitForRefreshOnDepositGroup(
depositGroupId: depositGroup.depositGroupId,
});
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups", "refreshGroups"],
+ { storeNames: ["depositGroups", "refreshGroups"] },
async (tx) => {
const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
let newOpState: DepositOperationStatus | undefined;
@@ -648,7 +665,7 @@ async function processDepositGroupPendingKyc(
kycStatusRes.status === HttpStatusCode.NoContent
) {
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const newDg = await tx.depositGroups.get(depositGroupId);
if (!newDg) {
@@ -707,7 +724,7 @@ async function transitionToKycRequired(
const kycStatus = await kycStatusReq.json();
logger.info(`kyc status: ${j2s(kycStatus)}`);
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
@@ -739,15 +756,27 @@ async function processDepositGroupPendingTrack(
wex: WalletExecutionContext,
depositGroup: DepositGroupRecord,
): Promise<TaskRunResult> {
+ const statusPerCoin = depositGroup.statusPerCoin;
+ const payCoinSelection = depositGroup.payCoinSelection;
+ if (!statusPerCoin) {
+ throw Error(
+ "unable to refund deposit group without coin selection (status missing)",
+ );
+ }
+ if (!payCoinSelection) {
+ throw Error(
+ "unable to refund deposit group without coin selection (selection missing)",
+ );
+ }
const { depositGroupId } = depositGroup;
- for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
- const coinPub = depositGroup.payCoinSelection.coinPubs[i];
+ for (let i = 0; i < statusPerCoin.length; i++) {
+ const coinPub = payCoinSelection.coinPubs[i];
// FIXME: Make the URL part of the coin selection?
const exchangeBaseUrl = await wex.db.runReadWriteTx(
- ["coins"],
+ { storeNames: ["coins"] },
async (tx) => {
const coinRecord = await tx.coins.get(coinPub);
- checkDbInvariant(!!coinRecord);
+ checkDbInvariant(!!coinRecord, `coin ${coinPub} not found in DB`);
return coinRecord.exchangeBaseUrl;
},
);
@@ -755,12 +784,12 @@ async function processDepositGroupPendingTrack(
let updatedTxStatus: DepositElementStatus | undefined = undefined;
let newWiredCoin:
| {
- id: string;
- value: DepositTrackingInfo;
- }
+ id: string;
+ value: DepositTrackingInfo;
+ }
| undefined;
- if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) {
+ if (statusPerCoin[i] !== DepositElementStatus.Wired) {
const track = await trackDeposit(
wex,
depositGroup,
@@ -820,46 +849,55 @@ async function processDepositGroupPendingTrack(
}
if (updatedTxStatus !== undefined) {
- await wex.db.runReadWriteTx(["depositGroups"], async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- return;
- }
- if (updatedTxStatus !== undefined) {
- dg.statusPerCoin[i] = updatedTxStatus;
- }
- if (newWiredCoin) {
- /**
- * FIXME: if there is a new wire information from the exchange
- * it should add up to the previous tracking states.
- *
- * This may loose information by overriding prev state.
- *
- * And: add checks to integration tests
- */
- if (!dg.trackingState) {
- dg.trackingState = {};
+ await wex.db.runReadWriteTx(
+ { storeNames: ["depositGroups"] },
+ async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return;
}
-
- dg.trackingState[newWiredCoin.id] = newWiredCoin.value;
- }
- await tx.depositGroups.put(dg);
- });
+ if (!dg.statusPerCoin) {
+ return;
+ }
+ if (updatedTxStatus !== undefined) {
+ dg.statusPerCoin[i] = updatedTxStatus;
+ }
+ if (newWiredCoin) {
+ /**
+ * FIXME: if there is a new wire information from the exchange
+ * it should add up to the previous tracking states.
+ *
+ * This may loose information by overriding prev state.
+ *
+ * And: add checks to integration tests
+ */
+ if (!dg.trackingState) {
+ dg.trackingState = {};
+ }
+
+ dg.trackingState[newWiredCoin.id] = newWiredCoin.value;
+ }
+ await tx.depositGroups.put(dg);
+ },
+ );
}
}
let allWired = true;
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
return undefined;
}
+ if (!dg.statusPerCoin) {
+ return undefined;
+ }
const oldTxState = computeDepositTransactionStatus(dg);
- for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
- if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) {
+ for (let i = 0; i < dg.statusPerCoin.length; i++) {
+ if (dg.statusPerCoin[i] !== DepositElementStatus.Wired) {
allWired = false;
break;
}
@@ -899,7 +937,7 @@ async function processDepositGroupPendingDeposit(
logger.info("processing deposit group in pending(deposit)");
const depositGroupId = depositGroup.depositGroupId;
const contractTermsRec = await wex.db.runReadOnlyTx(
- ["contractTerms"],
+ { storeNames: ["contractTerms"] },
async (tx) => {
return tx.contractTerms.get(depositGroup.contractTermsHash);
},
@@ -923,6 +961,88 @@ async function processDepositGroupPendingDeposit(
// Check for cancellation before expensive operations.
cancellationToken?.throwIfCancelled();
+ if (!depositGroup.payCoinSelection) {
+ logger.info("missing coin selection for deposit group, selecting now");
+ // FIXME: Consider doing the coin selection inside the txn
+ const payCoinSel = await selectPayCoins(wex, {
+ restrictExchanges: {
+ auditors: [],
+ exchanges: contractData.allowedExchanges,
+ },
+ restrictWireMethod: contractData.wireMethod,
+ contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+ depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
+ prevPayCoins: [],
+ });
+
+ switch (payCoinSel.type) {
+ case "success":
+ logger.info("coin selection success");
+ break;
+ case "failure":
+ logger.info("coin selection failure");
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
+ },
+ );
+ case "prospective":
+ logger.info("coin selection prospective");
+ throw Error("insufficient balance (waiting on pending refresh)");
+ default:
+ assertUnreachable(payCoinSel);
+ }
+
+ const transitionDone = await wex.db.runReadWriteTx(
+ {
+ storeNames: [
+ "depositGroups",
+ "coins",
+ "coinAvailability",
+ "refreshGroups",
+ "refreshSessions",
+ "denominations",
+ ],
+ },
+ async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return false;
+ }
+ if (dg.statusPerCoin) {
+ return false;
+ }
+ dg.payCoinSelection = {
+ coinContributions: payCoinSel.coinSel.coins.map(
+ (x) => x.contribution,
+ ),
+ coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
+ };
+ dg.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+ dg.statusPerCoin = payCoinSel.coinSel.coins.map(
+ () => DepositElementStatus.DepositPending,
+ );
+ await tx.depositGroups.put(dg);
+ await spendCoins(wex, tx, {
+ allocationId: transactionId,
+ coinPubs: dg.payCoinSelection.coinPubs,
+ contributions: dg.payCoinSelection.coinContributions.map((x) =>
+ Amounts.parseOrThrow(x),
+ ),
+ refreshReason: RefreshReason.PayDeposit,
+ });
+ return true;
+ },
+ );
+
+ if (transitionDone) {
+ return TaskRunResult.progress();
+ } else {
+ return TaskRunResult.backoff();
+ }
+ }
+
// FIXME: Cache these!
const depositPermissions = await generateDepositPermissions(
wex,
@@ -984,24 +1104,30 @@ async function processDepositGroupPendingDeposit(
codecForBatchDepositSuccess(),
);
- await wex.db.runReadWriteTx(["depositGroups"], async (tx) => {
- const dg = await tx.depositGroups.get(depositGroupId);
- if (!dg) {
- return;
- }
- for (const batchIndex of batchIndexes) {
- const coinStatus = dg.statusPerCoin[batchIndex];
- switch (coinStatus) {
- case DepositElementStatus.DepositPending:
- dg.statusPerCoin[batchIndex] = DepositElementStatus.Tracking;
- await tx.depositGroups.put(dg);
+ await wex.db.runReadWriteTx(
+ { storeNames: ["depositGroups"] },
+ async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return;
}
- }
- });
+ if (!dg.statusPerCoin) {
+ return;
+ }
+ for (const batchIndex of batchIndexes) {
+ const coinStatus = dg.statusPerCoin[batchIndex];
+ switch (coinStatus) {
+ case DepositElementStatus.DepositPending:
+ dg.statusPerCoin[batchIndex] = DepositElementStatus.Tracking;
+ await tx.depositGroups.put(dg);
+ }
+ }
+ },
+ );
}
const transitionInfo = await wex.db.runReadWriteTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
const dg = await tx.depositGroups.get(depositGroupId);
if (!dg) {
@@ -1027,7 +1153,7 @@ export async function processDepositGroup(
depositGroupId: string,
): Promise<TaskRunResult> {
const depositGroup = await wex.db.runReadOnlyTx(
- ["depositGroups"],
+ { storeNames: ["depositGroups"] },
async (tx) => {
return tx.depositGroups.get(depositGroupId);
},
@@ -1061,7 +1187,7 @@ async function getExchangeWireFee(
time: TalerProtocolTimestamp,
): Promise<WireFee> {
const exchangeDetails = await wex.db.runReadOnlyTx(
- ["exchangeDetails", "exchanges"],
+ { storeNames: ["exchangeDetails", "exchanges"] },
async (tx) => {
const ex = await tx.exchanges.get(baseUrl);
if (!ex || !ex.detailsPointer) return undefined;
@@ -1154,11 +1280,8 @@ async function trackDeposit(
/**
* Check if creating a deposit group is possible and calculate
* the associated fees.
- *
- * FIXME: This should be renamed to checkDepositGroup,
- * as it doesn't prepare anything
*/
-export async function prepareDepositGroup(
+export async function checkDepositGroup(
wex: WalletExecutionContext,
req: PrepareDepositRequest,
): Promise<PrepareDepositResponse> {
@@ -1167,22 +1290,26 @@ export async function prepareDepositGroup(
throw Error("invalid payto URI");
}
const amount = Amounts.parseOrThrow(req.amount);
+ const currency = Amounts.currencyOf(amount);
const exchangeInfos: ExchangeHandle[] = [];
- await wex.db.runReadOnlyTx(["exchangeDetails", "exchanges"], async (tx) => {
- const allExchanges = await tx.exchanges.iter().toArray();
- for (const e of allExchanges) {
- const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
- if (!details || amount.currency !== details.currency) {
- continue;
+ await wex.db.runReadOnlyTx(
+ { storeNames: ["exchangeDetails", "exchanges"] },
+ async (tx) => {
+ const allExchanges = await tx.exchanges.iter().toArray();
+ for (const e of allExchanges) {
+ const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
+ if (!details || amount.currency !== details.currency) {
+ continue;
+ }
+ exchangeInfos.push({
+ master_pub: details.masterPublicKey,
+ url: e.baseUrl,
+ });
}
- exchangeInfos.push({
- master_pub: details.masterPublicKey,
- url: e.baseUrl,
- });
- }
- });
+ },
+ );
const now = AbsoluteTime.now();
const nowRounded = AbsoluteTime.toProtocolTimestamp(now);
@@ -1226,32 +1353,42 @@ export async function prepareDepositGroup(
restrictWireMethod: contractData.wireMethod,
contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
- wireFeeAmortization: 1, // FIXME #8653
prevPayCoins: [],
});
- if (payCoinSel.type !== "success") {
- throw TalerError.fromDetail(
- TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
- {
- insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
- },
- );
+ let selCoins: SelectedProspectiveCoin[] | undefined = undefined;
+
+ switch (payCoinSel.type) {
+ case "failure":
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
+ },
+ );
+ case "prospective":
+ selCoins = payCoinSel.result.prospectiveCoins;
+ break;
+ case "success":
+ selCoins = payCoinSel.coinSel.coins;
+ break;
+ default:
+ assertUnreachable(payCoinSel);
}
- const totalDepositCost = await getTotalPaymentCost(wex, payCoinSel.coinSel);
+ const totalDepositCost = await getTotalPaymentCost(wex, currency, selCoins);
const effectiveDepositAmount = await getCounterpartyEffectiveDepositAmount(
wex,
p.targetType,
- payCoinSel.coinSel,
+ selCoins,
);
const fees = await getTotalFeesForDepositAmount(
wex,
p.targetType,
amount,
- payCoinSel.coinSel,
+ selCoins,
);
return {
@@ -1279,22 +1416,26 @@ export async function createDepositGroup(
}
const amount = Amounts.parseOrThrow(req.amount);
+ const currency = amount.currency;
const exchangeInfos: { url: string; master_pub: string }[] = [];
- await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => {
- const allExchanges = await tx.exchanges.iter().toArray();
- for (const e of allExchanges) {
- const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
- if (!details || amount.currency !== details.currency) {
- continue;
+ await wex.db.runReadOnlyTx(
+ { storeNames: ["exchanges", "exchangeDetails"] },
+ async (tx) => {
+ const allExchanges = await tx.exchanges.iter().toArray();
+ for (const e of allExchanges) {
+ const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
+ if (!details || amount.currency !== details.currency) {
+ continue;
+ }
+ exchangeInfos.push({
+ master_pub: details.masterPublicKey,
+ url: e.baseUrl,
+ });
}
- exchangeInfos.push({
- master_pub: details.masterPublicKey,
- url: e.baseUrl,
- });
- }
- });
+ },
+ );
const now = AbsoluteTime.now();
const wireDeadline = AbsoluteTime.toProtocolTimestamp(
@@ -1345,20 +1486,30 @@ export async function createDepositGroup(
restrictWireMethod: contractData.wireMethod,
contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
- wireFeeAmortization: 1, // FIXME #8653
prevPayCoins: [],
});
- if (payCoinSel.type !== "success") {
- throw TalerError.fromDetail(
- TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
- {
- insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
- },
- );
+ let coins: SelectedProspectiveCoin[] | undefined = undefined;
+
+ switch (payCoinSel.type) {
+ case "success":
+ coins = payCoinSel.coinSel.coins;
+ break;
+ case "failure":
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
+ },
+ );
+ case "prospective":
+ coins = payCoinSel.result.prospectiveCoins;
+ break;
+ default:
+ assertUnreachable(payCoinSel);
}
- const totalDepositCost = await getTotalPaymentCost(wex, payCoinSel.coinSel);
+ const totalDepositCost = await getTotalPaymentCost(wex, currency, coins);
let depositGroupId: string;
if (req.transactionId) {
@@ -1373,34 +1524,23 @@ export async function createDepositGroup(
const infoPerExchange: Record<string, DepositInfoPerExchange> = {};
- await wex.db.runReadOnlyTx(["coins"], async (tx) => {
- for (let i = 0; i < payCoinSel.coinSel.coins.length; i++) {
- const coin = await tx.coins.get(payCoinSel.coinSel.coins[i].coinPub);
- if (!coin) {
- logger.error("coin not found anymore");
- continue;
- }
- let depPerExchange = infoPerExchange[coin.exchangeBaseUrl];
- if (!depPerExchange) {
- infoPerExchange[coin.exchangeBaseUrl] = depPerExchange = {
- amountEffective: Amounts.stringify(
- Amounts.zeroOfAmount(totalDepositCost),
- ),
- };
- }
- const contrib = payCoinSel.coinSel.coins[i].contribution;
- depPerExchange.amountEffective = Amounts.stringify(
- Amounts.add(depPerExchange.amountEffective, contrib).amount,
- );
+ for (let i = 0; i < coins.length; i++) {
+ let depPerExchange = infoPerExchange[coins[i].exchangeBaseUrl];
+ if (!depPerExchange) {
+ infoPerExchange[coins[i].exchangeBaseUrl] = depPerExchange = {
+ amountEffective: Amounts.stringify(
+ Amounts.zeroOfAmount(totalDepositCost),
+ ),
+ };
}
- });
+ const contrib = coins[i].contribution;
+ depPerExchange.amountEffective = Amounts.stringify(
+ Amounts.add(depPerExchange.amountEffective, contrib).amount,
+ );
+ }
const counterpartyEffectiveDepositAmount =
- await getCounterpartyEffectiveDepositAmount(
- wex,
- p.targetType,
- payCoinSel.coinSel,
- );
+ await getCounterpartyEffectiveDepositAmount(wex, p.targetType, coins);
const depositGroup: DepositGroupRecord = {
contractTermsHash,
@@ -1413,14 +1553,9 @@ export async function createDepositGroup(
AbsoluteTime.toPreciseTimestamp(now),
),
timestampFinished: undefined,
- statusPerCoin: payCoinSel.coinSel.coins.map(
- () => DepositElementStatus.DepositPending,
- ),
- payCoinSelection: {
- coinContributions: payCoinSel.coinSel.coins.map((x) => x.contribution),
- coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
- },
- payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
+ statusPerCoin: undefined,
+ payCoinSelection: undefined,
+ payCoinSelectionUid: undefined,
merchantPriv: merchantPair.priv,
merchantPub: merchantPair.pub,
totalPayCost: Amounts.stringify(totalDepositCost),
@@ -1438,28 +1573,44 @@ export async function createDepositGroup(
infoPerExchange,
};
+ if (payCoinSel.type === "success") {
+ depositGroup.payCoinSelection = {
+ coinContributions: payCoinSel.coinSel.coins.map((x) => x.contribution),
+ coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
+ };
+ depositGroup.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+ depositGroup.statusPerCoin = payCoinSel.coinSel.coins.map(
+ () => DepositElementStatus.DepositPending,
+ );
+ }
+
const ctx = new DepositTransactionContext(wex, depositGroupId);
const transactionId = ctx.transactionId;
const newTxState = await wex.db.runReadWriteTx(
- [
- "depositGroups",
- "coins",
- "recoupGroups",
- "denominations",
- "refreshGroups",
- "coinAvailability",
- "contractTerms",
- ],
+ {
+ storeNames: [
+ "depositGroups",
+ "coins",
+ "recoupGroups",
+ "denominations",
+ "refreshGroups",
+ "refreshSessions",
+ "coinAvailability",
+ "contractTerms",
+ ],
+ },
async (tx) => {
- await spendCoins(wex, tx, {
- allocationId: transactionId,
- coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
- contributions: payCoinSel.coinSel.coins.map((x) =>
- Amounts.parseOrThrow(x.contribution),
- ),
- refreshReason: RefreshReason.PayDeposit,
- });
+ if (depositGroup.payCoinSelection) {
+ await spendCoins(wex, tx, {
+ allocationId: transactionId,
+ coinPubs: depositGroup.payCoinSelection.coinPubs,
+ contributions: depositGroup.payCoinSelection.coinContributions.map(
+ (x) => Amounts.parseOrThrow(x),
+ ),
+ refreshReason: RefreshReason.PayDeposit,
+ });
+ }
await tx.depositGroups.put(depositGroup);
await tx.contractTerms.put({
contractTermsRaw: contractTerms,
@@ -1498,32 +1649,28 @@ export async function createDepositGroup(
export async function getCounterpartyEffectiveDepositAmount(
wex: WalletExecutionContext,
wireType: string,
- pcs: PayCoinSelection,
+ pcs: SelectedProspectiveCoin[],
): Promise<AmountJson> {
const amt: AmountJson[] = [];
const fees: AmountJson[] = [];
const exchangeSet: Set<string> = new Set();
await wex.db.runReadOnlyTx(
- ["coins", "denominations", "exchangeDetails", "exchanges"],
+ { storeNames: ["coins", "denominations", "exchangeDetails", "exchanges"] },
async (tx) => {
- for (let i = 0; i < pcs.coins.length; i++) {
- const coin = await tx.coins.get(pcs.coins[i].coinPub);
- if (!coin) {
- throw Error("can't calculate deposit amount, coin not found");
- }
+ for (let i = 0; i < pcs.length; i++) {
const denom = await getDenomInfo(
wex,
tx,
- coin.exchangeBaseUrl,
- coin.denomPubHash,
+ pcs[i].exchangeBaseUrl,
+ pcs[i].denomPubHash,
);
if (!denom) {
throw Error("can't find denomination to calculate deposit amount");
}
- amt.push(Amounts.parseOrThrow(pcs.coins[i].contribution));
+ amt.push(Amounts.parseOrThrow(pcs[i].contribution));
fees.push(Amounts.parseOrThrow(denom.feeDeposit));
- exchangeSet.add(coin.exchangeBaseUrl);
+ exchangeSet.add(pcs[i].exchangeBaseUrl);
}
for (const exchangeUrl of exchangeSet.values()) {
@@ -1562,49 +1709,34 @@ async function getTotalFeesForDepositAmount(
wex: WalletExecutionContext,
wireType: string,
total: AmountJson,
- pcs: PayCoinSelection,
+ pcs: SelectedProspectiveCoin[],
): Promise<DepositGroupFees> {
const wireFee: AmountJson[] = [];
const coinFee: AmountJson[] = [];
const refreshFee: AmountJson[] = [];
const exchangeSet: Set<string> = new Set();
- const currency = Amounts.currencyOf(total);
await wex.db.runReadOnlyTx(
- ["coins", "denominations", "exchanges", "exchangeDetails"],
+ { storeNames: ["coins", "denominations", "exchanges", "exchangeDetails"] },
async (tx) => {
- for (let i = 0; i < pcs.coins.length; i++) {
- const coin = await tx.coins.get(pcs.coins[i].coinPub);
- if (!coin) {
- throw Error("can't calculate deposit amount, coin not found");
- }
+ for (let i = 0; i < pcs.length; i++) {
const denom = await getDenomInfo(
wex,
tx,
- coin.exchangeBaseUrl,
- coin.denomPubHash,
+ pcs[i].exchangeBaseUrl,
+ pcs[i].denomPubHash,
);
if (!denom) {
throw Error("can't find denomination to calculate deposit amount");
}
coinFee.push(Amounts.parseOrThrow(denom.feeDeposit));
- exchangeSet.add(coin.exchangeBaseUrl);
-
- const allDenoms = await getCandidateWithdrawalDenomsTx(
+ exchangeSet.add(pcs[i].exchangeBaseUrl);
+ const amountLeft = Amounts.sub(denom.value, pcs[i].contribution).amount;
+ const refreshCost = await getTotalRefreshCost(
wex,
tx,
- coin.exchangeBaseUrl,
- currency,
- );
- const amountLeft = Amounts.sub(
- denom.value,
- pcs.coins[i].contribution,
- ).amount;
- const refreshCost = getTotalRefreshCost(
- allDenoms,
denom,
amountLeft,
- wex.ws.config.testing.denomselAllowLate,
);
refreshFee.push(refreshCost);
}