diff options
Diffstat (limited to 'packages/taler-wallet-core/src/deposits.ts')
-rw-r--r-- | packages/taler-wallet-core/src/deposits.ts | 566 |
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); } |