wallet-core: fix deposit tx states, long-poll on kyc
This commit is contained in:
parent
66432cdd05
commit
fca893038d
@ -910,14 +910,6 @@ export enum RefreshOperationStatus {
|
|||||||
Failed = 51 /* DORMANT_START + 1 */,
|
Failed = 51 /* DORMANT_START + 1 */,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DepositGroupOperationStatus {
|
|
||||||
Pending = 10,
|
|
||||||
AbortingWithRefresh = 11,
|
|
||||||
|
|
||||||
Finished = 50,
|
|
||||||
Failed = 51,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of a single element of a deposit group.
|
* Status of a single element of a deposit group.
|
||||||
*/
|
*/
|
||||||
@ -1653,11 +1645,15 @@ export interface BackupProviderRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum DepositOperationStatus {
|
export enum DepositOperationStatus {
|
||||||
Pending = 10,
|
PendingDeposit = 10,
|
||||||
Aborting = 11,
|
Aborting = 11,
|
||||||
|
PendingTrack = 12,
|
||||||
|
PendingKyc = 13,
|
||||||
|
|
||||||
Suspended = 20,
|
SuspendedDeposit = 20,
|
||||||
SuspendedAborting = 21,
|
SuspendedAborting = 21,
|
||||||
|
SuspendedTrack = 22,
|
||||||
|
SuspendedKyc = 23,
|
||||||
|
|
||||||
Finished = 50,
|
Finished = 50,
|
||||||
Failed = 51,
|
Failed = 51,
|
||||||
@ -1737,12 +1733,22 @@ export interface DepositGroupRecord {
|
|||||||
*/
|
*/
|
||||||
abortRefreshGroupId?: string;
|
abortRefreshGroupId?: string;
|
||||||
|
|
||||||
|
kycInfo?: DepositKycInfo;
|
||||||
|
|
||||||
// FIXME: Do we need this and should it be in this object store?
|
// FIXME: Do we need this and should it be in this object store?
|
||||||
trackingState?: {
|
trackingState?: {
|
||||||
[signature: string]: DepositTrackingInfo;
|
[signature: string]: DepositTrackingInfo;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DepositKycInfo {
|
||||||
|
kycUrl: string;
|
||||||
|
requirementRow: number;
|
||||||
|
paytoHash: string;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record for a deposits that the wallet observed
|
* Record for a deposits that the wallet observed
|
||||||
* as a result of double spending, but which is not
|
* as a result of double spending, but which is not
|
||||||
|
@ -108,7 +108,6 @@ function computeRefreshGroupAvailableAmount(r: RefreshGroupRecord): AmountJson {
|
|||||||
export async function getBalancesInsideTransaction(
|
export async function getBalancesInsideTransaction(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
tx: GetReadOnlyAccess<{
|
tx: GetReadOnlyAccess<{
|
||||||
coins: typeof WalletStoresV1.coins;
|
|
||||||
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
coinAvailability: typeof WalletStoresV1.coinAvailability;
|
||||||
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
refreshGroups: typeof WalletStoresV1.refreshGroups;
|
||||||
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
|
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021-2023 Taler Systems S.A.
|
||||||
|
|
||||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
terms of the GNU General Public License as published by the Free Software
|
terms of the GNU General Public License as published by the Free Software
|
||||||
@ -83,6 +83,7 @@ import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
|||||||
import {
|
import {
|
||||||
constructTaskIdentifier,
|
constructTaskIdentifier,
|
||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
|
runLongpollAsync,
|
||||||
spendCoins,
|
spendCoins,
|
||||||
TombstoneTag,
|
TombstoneTag,
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
@ -100,6 +101,7 @@ import {
|
|||||||
stopLongpolling,
|
stopLongpolling,
|
||||||
} from "./transactions.js";
|
} from "./transactions.js";
|
||||||
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
|
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
|
||||||
|
import { assertUnreachable } from "../util/assertUnreachable.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logger.
|
* Logger.
|
||||||
@ -114,51 +116,36 @@ export function computeDepositTransactionStatus(
|
|||||||
dg: DepositGroupRecord,
|
dg: DepositGroupRecord,
|
||||||
): TransactionState {
|
): TransactionState {
|
||||||
switch (dg.operationStatus) {
|
switch (dg.operationStatus) {
|
||||||
case DepositOperationStatus.Finished: {
|
case DepositOperationStatus.Finished:
|
||||||
return {
|
return {
|
||||||
major: TransactionMajorState.Done,
|
major: TransactionMajorState.Done,
|
||||||
};
|
};
|
||||||
}
|
case DepositOperationStatus.PendingDeposit:
|
||||||
// FIXME: We should actually use separate pending states for this!
|
|
||||||
case DepositOperationStatus.Pending: {
|
|
||||||
const numTotal = dg.payCoinSelection.coinPubs.length;
|
|
||||||
let numDeposited = 0;
|
|
||||||
let numKycRequired = 0;
|
|
||||||
let numWired = 0;
|
|
||||||
for (let i = 0; i < numTotal; i++) {
|
|
||||||
if (dg.depositedPerCoin[i]) {
|
|
||||||
numDeposited++;
|
|
||||||
}
|
|
||||||
switch (dg.transactionPerCoin[i]) {
|
|
||||||
case DepositElementStatus.KycRequired:
|
|
||||||
numKycRequired++;
|
|
||||||
break;
|
|
||||||
case DepositElementStatus.Wired:
|
|
||||||
numWired++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numKycRequired > 0) {
|
|
||||||
return {
|
|
||||||
major: TransactionMajorState.Pending,
|
|
||||||
minor: TransactionMinorState.KycRequired,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numDeposited == numTotal) {
|
|
||||||
return {
|
|
||||||
major: TransactionMajorState.Pending,
|
|
||||||
minor: TransactionMinorState.Track,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
major: TransactionMajorState.Pending,
|
major: TransactionMajorState.Pending,
|
||||||
minor: TransactionMinorState.Deposit,
|
minor: TransactionMinorState.Deposit,
|
||||||
};
|
};
|
||||||
}
|
case DepositOperationStatus.PendingKyc:
|
||||||
case DepositOperationStatus.Suspended:
|
return {
|
||||||
|
major: TransactionMajorState.Pending,
|
||||||
|
minor: TransactionMinorState.KycRequired,
|
||||||
|
};
|
||||||
|
case DepositOperationStatus.PendingTrack:
|
||||||
|
return {
|
||||||
|
major: TransactionMajorState.Pending,
|
||||||
|
minor: TransactionMinorState.Track,
|
||||||
|
};
|
||||||
|
case DepositOperationStatus.SuspendedKyc:
|
||||||
|
return {
|
||||||
|
major: TransactionMajorState.Suspended,
|
||||||
|
minor: TransactionMinorState.KycRequired,
|
||||||
|
};
|
||||||
|
case DepositOperationStatus.SuspendedTrack:
|
||||||
|
return {
|
||||||
|
major: TransactionMajorState.Suspended,
|
||||||
|
minor: TransactionMinorState.Track,
|
||||||
|
};
|
||||||
|
case DepositOperationStatus.SuspendedDeposit:
|
||||||
return {
|
return {
|
||||||
major: TransactionMajorState.Suspended,
|
major: TransactionMajorState.Suspended,
|
||||||
};
|
};
|
||||||
@ -179,7 +166,7 @@ export function computeDepositTransactionStatus(
|
|||||||
major: TransactionMajorState.SuspendedAborting,
|
major: TransactionMajorState.SuspendedAborting,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
throw Error(`unexpected deposit group state (${dg.operationStatus})`);
|
assertUnreachable(dg.operationStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,39 +174,11 @@ export function computeDepositTransactionActions(
|
|||||||
dg: DepositGroupRecord,
|
dg: DepositGroupRecord,
|
||||||
): TransactionAction[] {
|
): TransactionAction[] {
|
||||||
switch (dg.operationStatus) {
|
switch (dg.operationStatus) {
|
||||||
case DepositOperationStatus.Finished: {
|
case DepositOperationStatus.Finished:
|
||||||
return [TransactionAction.Delete];
|
return [TransactionAction.Delete];
|
||||||
}
|
case DepositOperationStatus.PendingDeposit:
|
||||||
case DepositOperationStatus.Pending: {
|
|
||||||
const numTotal = dg.payCoinSelection.coinPubs.length;
|
|
||||||
let numDeposited = 0;
|
|
||||||
let numKycRequired = 0;
|
|
||||||
let numWired = 0;
|
|
||||||
for (let i = 0; i < numTotal; i++) {
|
|
||||||
if (dg.depositedPerCoin[i]) {
|
|
||||||
numDeposited++;
|
|
||||||
}
|
|
||||||
switch (dg.transactionPerCoin[i]) {
|
|
||||||
case DepositElementStatus.KycRequired:
|
|
||||||
numKycRequired++;
|
|
||||||
break;
|
|
||||||
case DepositElementStatus.Wired:
|
|
||||||
numWired++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numKycRequired > 0) {
|
|
||||||
return [TransactionAction.Suspend, TransactionAction.Fail];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numDeposited == numTotal) {
|
|
||||||
return [TransactionAction.Suspend, TransactionAction.Fail];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [TransactionAction.Suspend, TransactionAction.Abort];
|
return [TransactionAction.Suspend, TransactionAction.Abort];
|
||||||
}
|
case DepositOperationStatus.SuspendedDeposit:
|
||||||
case DepositOperationStatus.Suspended:
|
|
||||||
return [TransactionAction.Resume];
|
return [TransactionAction.Resume];
|
||||||
case DepositOperationStatus.Aborting:
|
case DepositOperationStatus.Aborting:
|
||||||
return [TransactionAction.Fail, TransactionAction.Suspend];
|
return [TransactionAction.Fail, TransactionAction.Suspend];
|
||||||
@ -229,8 +188,16 @@ export function computeDepositTransactionActions(
|
|||||||
return [TransactionAction.Delete];
|
return [TransactionAction.Delete];
|
||||||
case DepositOperationStatus.SuspendedAborting:
|
case DepositOperationStatus.SuspendedAborting:
|
||||||
return [TransactionAction.Resume, TransactionAction.Fail];
|
return [TransactionAction.Resume, TransactionAction.Fail];
|
||||||
|
case DepositOperationStatus.PendingKyc:
|
||||||
|
return [TransactionAction.Suspend, TransactionAction.Fail];
|
||||||
|
case DepositOperationStatus.PendingTrack:
|
||||||
|
return [TransactionAction.Suspend, TransactionAction.Abort];
|
||||||
|
case DepositOperationStatus.SuspendedKyc:
|
||||||
|
return [TransactionAction.Resume, TransactionAction.Fail];
|
||||||
|
case DepositOperationStatus.SuspendedTrack:
|
||||||
|
return [TransactionAction.Resume, TransactionAction.Abort];
|
||||||
default:
|
default:
|
||||||
throw Error(`unexpected deposit group state (${dg.operationStatus})`);
|
assertUnreachable(dg.operationStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,15 +227,15 @@ export async function suspendDepositGroup(
|
|||||||
switch (dg.operationStatus) {
|
switch (dg.operationStatus) {
|
||||||
case DepositOperationStatus.Finished:
|
case DepositOperationStatus.Finished:
|
||||||
return undefined;
|
return undefined;
|
||||||
case DepositOperationStatus.Pending: {
|
case DepositOperationStatus.PendingDeposit: {
|
||||||
dg.operationStatus = DepositOperationStatus.Suspended;
|
dg.operationStatus = DepositOperationStatus.SuspendedDeposit;
|
||||||
await tx.depositGroups.put(dg);
|
await tx.depositGroups.put(dg);
|
||||||
return {
|
return {
|
||||||
oldTxState: oldState,
|
oldTxState: oldState,
|
||||||
newTxState: computeDepositTransactionStatus(dg),
|
newTxState: computeDepositTransactionStatus(dg),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case DepositOperationStatus.Suspended:
|
case DepositOperationStatus.SuspendedDeposit:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -299,11 +266,11 @@ export async function resumeDepositGroup(
|
|||||||
switch (dg.operationStatus) {
|
switch (dg.operationStatus) {
|
||||||
case DepositOperationStatus.Finished:
|
case DepositOperationStatus.Finished:
|
||||||
return;
|
return;
|
||||||
case DepositOperationStatus.Pending: {
|
case DepositOperationStatus.PendingDeposit: {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case DepositOperationStatus.Suspended:
|
case DepositOperationStatus.SuspendedDeposit:
|
||||||
dg.operationStatus = DepositOperationStatus.Pending;
|
dg.operationStatus = DepositOperationStatus.PendingDeposit;
|
||||||
await tx.depositGroups.put(dg);
|
await tx.depositGroups.put(dg);
|
||||||
return {
|
return {
|
||||||
oldTxState: oldState,
|
oldTxState: oldState,
|
||||||
@ -342,7 +309,7 @@ export async function abortDepositGroup(
|
|||||||
switch (dg.operationStatus) {
|
switch (dg.operationStatus) {
|
||||||
case DepositOperationStatus.Finished:
|
case DepositOperationStatus.Finished:
|
||||||
return undefined;
|
return undefined;
|
||||||
case DepositOperationStatus.Pending: {
|
case DepositOperationStatus.PendingDeposit: {
|
||||||
dg.operationStatus = DepositOperationStatus.Aborting;
|
dg.operationStatus = DepositOperationStatus.Aborting;
|
||||||
await tx.depositGroups.put(dg);
|
await tx.depositGroups.put(dg);
|
||||||
return {
|
return {
|
||||||
@ -350,7 +317,7 @@ export async function abortDepositGroup(
|
|||||||
newTxState: computeDepositTransactionStatus(dg),
|
newTxState: computeDepositTransactionStatus(dg),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case DepositOperationStatus.Suspended:
|
case DepositOperationStatus.SuspendedDeposit:
|
||||||
// FIXME: Can we abort a suspended transaction?!
|
// FIXME: Can we abort a suspended transaction?!
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -633,95 +600,167 @@ async function refundDepositGroup(
|
|||||||
return OperationAttemptResult.pendingEmpty();
|
return OperationAttemptResult.pendingEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function processDepositGroupAborting(
|
||||||
* Process a deposit group that is not in its final state yet.
|
|
||||||
*/
|
|
||||||
export async function processDepositGroup(
|
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
depositGroupId: string,
|
depositGroup: DepositGroupRecord,
|
||||||
options: {
|
|
||||||
cancellationToken?: CancellationToken;
|
|
||||||
} = {},
|
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
const depositGroup = await ws.db
|
logger.info("processing deposit tx in 'aborting'");
|
||||||
.mktx((x) => [x.depositGroups])
|
const abortRefreshGroupId = depositGroup.abortRefreshGroupId;
|
||||||
.runReadOnly(async (tx) => {
|
if (!abortRefreshGroupId) {
|
||||||
return tx.depositGroups.get(depositGroupId);
|
logger.info("refunding deposit group");
|
||||||
|
return refundDepositGroup(ws, depositGroup);
|
||||||
|
}
|
||||||
|
logger.info("waiting for refresh");
|
||||||
|
return waitForRefreshOnDepositGroup(ws, depositGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processDepositGroupPendingKyc(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
depositGroup: DepositGroupRecord,
|
||||||
|
): Promise<OperationAttemptResult> {
|
||||||
|
const { depositGroupId } = depositGroup;
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Deposit,
|
||||||
|
depositGroupId,
|
||||||
});
|
});
|
||||||
if (!depositGroup) {
|
const retryTag = constructTaskIdentifier({
|
||||||
logger.warn(`deposit group ${depositGroupId} not found`);
|
tag: PendingTaskType.Deposit,
|
||||||
return OperationAttemptResult.finishedEmpty();
|
depositGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const kycInfo = depositGroup.kycInfo;
|
||||||
|
const userType = "individual";
|
||||||
|
|
||||||
|
if (!kycInfo) {
|
||||||
|
throw Error("invalid DB state, in pending(kyc), but no kycInfo present");
|
||||||
}
|
}
|
||||||
if (depositGroup.timestampFinished) {
|
|
||||||
logger.trace(`deposit group ${depositGroupId} already finished`);
|
runLongpollAsync(ws, retryTag, async (ct) => {
|
||||||
return OperationAttemptResult.finishedEmpty();
|
const url = new URL(
|
||||||
|
`kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/${userType}`,
|
||||||
|
kycInfo.exchangeBaseUrl,
|
||||||
|
);
|
||||||
|
url.searchParams.set("timeout_ms", "10000");
|
||||||
|
logger.info(`kyc url ${url.href}`);
|
||||||
|
const kycStatusRes = await ws.http.fetch(url.href, {
|
||||||
|
method: "GET",
|
||||||
|
cancellationToken: ct,
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
kycStatusRes.status === HttpStatusCode.Ok ||
|
||||||
|
//FIXME: NoContent is not expected https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge
|
||||||
|
// remove after the exchange is fixed or clarified
|
||||||
|
kycStatusRes.status === HttpStatusCode.NoContent
|
||||||
|
) {
|
||||||
|
const transitionInfo = await ws.db
|
||||||
|
.mktx((x) => [x.depositGroups])
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
|
const newDg = await tx.depositGroups.get(depositGroupId);
|
||||||
|
if (!newDg) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (newDg.operationStatus !== DepositOperationStatus.PendingKyc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oldTxState = computeDepositTransactionStatus(newDg);
|
||||||
|
newDg.operationStatus = DepositOperationStatus.PendingTrack;
|
||||||
|
const newTxState = computeDepositTransactionStatus(newDg);
|
||||||
|
await tx.depositGroups.put(newDg);
|
||||||
|
return { oldTxState, newTxState };
|
||||||
|
});
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
return { ready: true };
|
||||||
|
} else if (kycStatusRes.status === HttpStatusCode.Accepted) {
|
||||||
|
// FIXME: Do we have to update the URL here?
|
||||||
|
return { ready: false };
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
`unexpected response from kyc-check (${kycStatusRes.status})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return OperationAttemptResult.longpoll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking information from the exchange indicated that
|
||||||
|
* KYC is required. We need to check the KYC info
|
||||||
|
* and transition the transaction to the KYC required state.
|
||||||
|
*/
|
||||||
|
async function transitionToKycRequired(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
depositGroup: DepositGroupRecord,
|
||||||
|
kycInfo: KycPendingInfo,
|
||||||
|
exchangeUrl: string,
|
||||||
|
): Promise<OperationAttemptResult> {
|
||||||
|
const { depositGroupId } = depositGroup;
|
||||||
|
const userType = "individual";
|
||||||
|
|
||||||
const transactionId = constructTransactionIdentifier({
|
const transactionId = constructTransactionIdentifier({
|
||||||
tag: TransactionType.Deposit,
|
tag: TransactionType.Deposit,
|
||||||
depositGroupId,
|
depositGroupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const txStateOld = computeDepositTransactionStatus(depositGroup);
|
|
||||||
|
|
||||||
if (depositGroup.operationStatus === DepositOperationStatus.Pending) {
|
|
||||||
const contractData = extractContractData(
|
|
||||||
depositGroup.contractTermsRaw,
|
|
||||||
depositGroup.contractTermsHash,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for cancellation before expensive operations.
|
|
||||||
options.cancellationToken?.throwIfCancelled();
|
|
||||||
// FIXME: Cache these!
|
|
||||||
const depositPermissions = await generateDepositPermissions(
|
|
||||||
ws,
|
|
||||||
depositGroup.payCoinSelection,
|
|
||||||
contractData,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 0; i < depositPermissions.length; i++) {
|
|
||||||
const perm = depositPermissions[i];
|
|
||||||
|
|
||||||
let didDeposit: boolean = false;
|
|
||||||
|
|
||||||
if (!depositGroup.depositedPerCoin[i]) {
|
|
||||||
const requestBody: ExchangeDepositRequest = {
|
|
||||||
contribution: Amounts.stringify(perm.contribution),
|
|
||||||
merchant_payto_uri: depositGroup.wire.payto_uri,
|
|
||||||
wire_salt: depositGroup.wire.salt,
|
|
||||||
h_contract_terms: depositGroup.contractTermsHash,
|
|
||||||
ub_sig: perm.ub_sig,
|
|
||||||
timestamp: depositGroup.contractTermsRaw.timestamp,
|
|
||||||
wire_transfer_deadline:
|
|
||||||
depositGroup.contractTermsRaw.wire_transfer_deadline,
|
|
||||||
refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
|
|
||||||
coin_sig: perm.coin_sig,
|
|
||||||
denom_pub_hash: perm.h_denom,
|
|
||||||
merchant_pub: depositGroup.merchantPub,
|
|
||||||
h_age_commitment: perm.h_age_commitment,
|
|
||||||
};
|
|
||||||
// Check for cancellation before making network request.
|
|
||||||
options.cancellationToken?.throwIfCancelled();
|
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`coins/${perm.coin_pub}/deposit`,
|
`kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/${userType}`,
|
||||||
perm.exchange_url,
|
exchangeUrl,
|
||||||
);
|
);
|
||||||
logger.info(`depositing to ${url}`);
|
logger.info(`kyc url ${url.href}`);
|
||||||
const httpResp = await ws.http.fetch(url.href, {
|
const kycStatusReq = await ws.http.fetch(url.href, {
|
||||||
method: "POST",
|
method: "GET",
|
||||||
body: requestBody,
|
|
||||||
cancellationToken: options.cancellationToken,
|
|
||||||
});
|
});
|
||||||
await readSuccessResponseJsonOrThrow(
|
if (kycStatusReq.status === HttpStatusCode.Ok) {
|
||||||
httpResp,
|
logger.warn("kyc requested, but already fulfilled");
|
||||||
codecForDepositSuccess(),
|
return OperationAttemptResult.finishedEmpty();
|
||||||
);
|
} else if (kycStatusReq.status === HttpStatusCode.Accepted) {
|
||||||
didDeposit = true;
|
const kycStatus = await kycStatusReq.json();
|
||||||
|
logger.info(`kyc status: ${j2s(kycStatus)}`);
|
||||||
|
const transitionInfo = await ws.db
|
||||||
|
.mktx((x) => [x.depositGroups])
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
|
const dg = await tx.depositGroups.get(depositGroupId);
|
||||||
|
if (!dg) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
if (dg.operationStatus !== DepositOperationStatus.PendingTrack) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const oldTxState = computeDepositTransactionStatus(dg);
|
||||||
|
dg.kycInfo = {
|
||||||
|
exchangeBaseUrl: exchangeUrl,
|
||||||
|
kycUrl: kycStatus.kyc_url,
|
||||||
|
paytoHash: kycInfo.paytoHash,
|
||||||
|
requirementRow: kycInfo.requirementRow,
|
||||||
|
};
|
||||||
|
await tx.depositGroups.put(dg);
|
||||||
|
const newTxState = computeDepositTransactionStatus(dg);
|
||||||
|
return { oldTxState, newTxState };
|
||||||
|
});
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
return OperationAttemptResult.finishedEmpty();
|
||||||
|
} else {
|
||||||
|
throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processDepositGroupPendingTrack(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
depositGroup: DepositGroupRecord,
|
||||||
|
cancellationToken?: CancellationToken,
|
||||||
|
): Promise<OperationAttemptResult> {
|
||||||
|
const { depositGroupId } = depositGroup;
|
||||||
|
for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
|
||||||
|
const coinPub = depositGroup.payCoinSelection.coinPubs[i];
|
||||||
|
// FIXME: Make the URL part of the coin selection?
|
||||||
|
const exchangeBaseUrl = await ws.db
|
||||||
|
.mktx((x) => [x.coins])
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
|
const coinRecord = await tx.coins.get(coinPub);
|
||||||
|
checkDbInvariant(!!coinRecord);
|
||||||
|
return coinRecord.exchangeBaseUrl;
|
||||||
|
});
|
||||||
|
|
||||||
let updatedTxStatus: DepositElementStatus | undefined = undefined;
|
let updatedTxStatus: DepositElementStatus | undefined = undefined;
|
||||||
|
|
||||||
let newWiredCoin:
|
let newWiredCoin:
|
||||||
| {
|
| {
|
||||||
id: string;
|
id: string;
|
||||||
@ -730,20 +769,28 @@ export async function processDepositGroup(
|
|||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
if (depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired) {
|
if (depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired) {
|
||||||
const track = await trackDeposit(ws, depositGroup, perm);
|
const track = await trackDeposit(
|
||||||
|
ws,
|
||||||
|
depositGroup,
|
||||||
|
coinPub,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
);
|
||||||
|
|
||||||
if (track.type === "accepted") {
|
if (track.type === "accepted") {
|
||||||
if (!track.kyc_ok && track.requirement_row !== undefined) {
|
if (!track.kyc_ok && track.requirement_row !== undefined) {
|
||||||
updatedTxStatus = DepositElementStatus.KycRequired;
|
|
||||||
const { requirement_row: requirementRow } = track;
|
|
||||||
const paytoHash = encodeCrock(
|
const paytoHash = encodeCrock(
|
||||||
hashTruncate32(stringToBytes(depositGroup.wire.payto_uri + "\0")),
|
hashTruncate32(stringToBytes(depositGroup.wire.payto_uri + "\0")),
|
||||||
);
|
);
|
||||||
await checkDepositKycStatus(
|
const { requirement_row: requirementRow } = track;
|
||||||
|
const kycInfo: KycPendingInfo = {
|
||||||
|
paytoHash,
|
||||||
|
requirementRow,
|
||||||
|
};
|
||||||
|
return transitionToKycRequired(
|
||||||
ws,
|
ws,
|
||||||
perm.exchange_url,
|
depositGroup,
|
||||||
{ paytoHash, requirementRow },
|
kycInfo,
|
||||||
"individual",
|
exchangeBaseUrl,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
updatedTxStatus = DepositElementStatus.Accepted;
|
updatedTxStatus = DepositElementStatus.Accepted;
|
||||||
@ -759,7 +806,7 @@ export async function processDepositGroup(
|
|||||||
const fee = await getExchangeWireFee(
|
const fee = await getExchangeWireFee(
|
||||||
ws,
|
ws,
|
||||||
payto.targetType,
|
payto.targetType,
|
||||||
perm.exchange_url,
|
exchangeBaseUrl,
|
||||||
track.execution_time,
|
track.execution_time,
|
||||||
);
|
);
|
||||||
const raw = Amounts.parseOrThrow(track.coin_contribution);
|
const raw = Amounts.parseOrThrow(track.coin_contribution);
|
||||||
@ -780,7 +827,7 @@ export async function processDepositGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedTxStatus !== undefined || didDeposit) {
|
if (updatedTxStatus !== undefined) {
|
||||||
await ws.db
|
await ws.db
|
||||||
.mktx((x) => [x.depositGroups])
|
.mktx((x) => [x.depositGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
@ -788,9 +835,6 @@ export async function processDepositGroup(
|
|||||||
if (!dg) {
|
if (!dg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (didDeposit) {
|
|
||||||
dg.depositedPerCoin[i] = didDeposit;
|
|
||||||
}
|
|
||||||
if (updatedTxStatus !== undefined) {
|
if (updatedTxStatus !== undefined) {
|
||||||
dg.transactionPerCoin[i] = updatedTxStatus;
|
dg.transactionPerCoin[i] = updatedTxStatus;
|
||||||
}
|
}
|
||||||
@ -814,70 +858,173 @@ export async function processDepositGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const txStatusNew = await ws.db
|
let allWired = true;
|
||||||
|
|
||||||
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.depositGroups])
|
.mktx((x) => [x.depositGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const dg = await tx.depositGroups.get(depositGroupId);
|
const dg = await tx.depositGroups.get(depositGroupId);
|
||||||
if (!dg) {
|
if (!dg) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
let allDepositedAndWired = true;
|
const oldTxState = computeDepositTransactionStatus(dg);
|
||||||
for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
|
for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
|
||||||
if (
|
if (depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired) {
|
||||||
!depositGroup.depositedPerCoin[i] ||
|
allWired = false;
|
||||||
depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired
|
|
||||||
) {
|
|
||||||
allDepositedAndWired = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (allDepositedAndWired) {
|
if (allWired) {
|
||||||
dg.timestampFinished = TalerPreciseTimestamp.now();
|
dg.timestampFinished = TalerPreciseTimestamp.now();
|
||||||
dg.operationStatus = DepositOperationStatus.Finished;
|
dg.operationStatus = DepositOperationStatus.Finished;
|
||||||
await tx.depositGroups.put(dg);
|
await tx.depositGroups.put(dg);
|
||||||
}
|
}
|
||||||
return computeDepositTransactionStatus(dg);
|
const newTxState = computeDepositTransactionStatus(dg);
|
||||||
|
return { oldTxState, newTxState };
|
||||||
});
|
});
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
if (!txStatusNew) {
|
tag: TransactionType.Deposit,
|
||||||
// Doesn't exist anymore!
|
depositGroupId,
|
||||||
|
});
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
if (allWired) {
|
||||||
return OperationAttemptResult.finishedEmpty();
|
return OperationAttemptResult.finishedEmpty();
|
||||||
}
|
|
||||||
|
|
||||||
// Notify if state transitioned
|
|
||||||
if (
|
|
||||||
txStateOld.major !== txStatusNew.major ||
|
|
||||||
txStateOld.minor !== txStatusNew.minor
|
|
||||||
) {
|
|
||||||
ws.notify({
|
|
||||||
type: NotificationType.TransactionStateTransition,
|
|
||||||
transactionId,
|
|
||||||
oldTxState: txStateOld,
|
|
||||||
newTxState: txStatusNew,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: consider other cases like aborting, suspend, ...
|
|
||||||
if (
|
|
||||||
txStatusNew.major === TransactionMajorState.Pending ||
|
|
||||||
txStatusNew.major === TransactionMajorState.Aborting
|
|
||||||
) {
|
|
||||||
return OperationAttemptResult.pendingEmpty();
|
|
||||||
} else {
|
} else {
|
||||||
return OperationAttemptResult.finishedEmpty();
|
// FIXME: Use long-polling.
|
||||||
|
return OperationAttemptResult.pendingEmpty();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processDepositGroupPendingDeposit(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
depositGroup: DepositGroupRecord,
|
||||||
|
cancellationToken?: CancellationToken,
|
||||||
|
): Promise<OperationAttemptResult> {
|
||||||
|
logger.info("processing deposit group in pending(deposit)");
|
||||||
|
const depositGroupId = depositGroup.depositGroupId;
|
||||||
|
const contractData = extractContractData(
|
||||||
|
depositGroup.contractTermsRaw,
|
||||||
|
depositGroup.contractTermsHash,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Deposit,
|
||||||
|
depositGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for cancellation before expensive operations.
|
||||||
|
cancellationToken?.throwIfCancelled();
|
||||||
|
|
||||||
|
// FIXME: Cache these!
|
||||||
|
const depositPermissions = await generateDepositPermissions(
|
||||||
|
ws,
|
||||||
|
depositGroup.payCoinSelection,
|
||||||
|
contractData,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < depositPermissions.length; i++) {
|
||||||
|
const perm = depositPermissions[i];
|
||||||
|
|
||||||
|
if (depositGroup.depositedPerCoin[i]) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (depositGroup.operationStatus === DepositOperationStatus.Aborting) {
|
const requestBody: ExchangeDepositRequest = {
|
||||||
logger.info("processing deposit tx in 'aborting'");
|
contribution: Amounts.stringify(perm.contribution),
|
||||||
const abortRefreshGroupId = depositGroup.abortRefreshGroupId;
|
merchant_payto_uri: depositGroup.wire.payto_uri,
|
||||||
if (!abortRefreshGroupId) {
|
wire_salt: depositGroup.wire.salt,
|
||||||
logger.info("refunding deposit group");
|
h_contract_terms: depositGroup.contractTermsHash,
|
||||||
return refundDepositGroup(ws, depositGroup);
|
ub_sig: perm.ub_sig,
|
||||||
|
timestamp: depositGroup.contractTermsRaw.timestamp,
|
||||||
|
wire_transfer_deadline:
|
||||||
|
depositGroup.contractTermsRaw.wire_transfer_deadline,
|
||||||
|
refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
|
||||||
|
coin_sig: perm.coin_sig,
|
||||||
|
denom_pub_hash: perm.h_denom,
|
||||||
|
merchant_pub: depositGroup.merchantPub,
|
||||||
|
h_age_commitment: perm.h_age_commitment,
|
||||||
|
};
|
||||||
|
// Check for cancellation before making network request.
|
||||||
|
cancellationToken?.throwIfCancelled();
|
||||||
|
const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
|
||||||
|
logger.info(`depositing to ${url}`);
|
||||||
|
const httpResp = await ws.http.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: requestBody,
|
||||||
|
cancellationToken: cancellationToken,
|
||||||
|
});
|
||||||
|
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
|
||||||
|
|
||||||
|
await ws.db
|
||||||
|
.mktx((x) => [x.depositGroups])
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
|
const dg = await tx.depositGroups.get(depositGroupId);
|
||||||
|
if (!dg) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
logger.info("waiting for refresh");
|
dg.depositedPerCoin[i] = true;
|
||||||
return waitForRefreshOnDepositGroup(ws, depositGroup);
|
await tx.depositGroups.put(dg);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transitionInfo = await ws.db
|
||||||
|
.mktx((x) => [x.depositGroups])
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
|
const dg = await tx.depositGroups.get(depositGroupId);
|
||||||
|
if (!dg) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const oldTxState = computeDepositTransactionStatus(dg);
|
||||||
|
dg.operationStatus = DepositOperationStatus.PendingTrack;
|
||||||
|
await tx.depositGroups.put(dg);
|
||||||
|
const newTxState = computeDepositTransactionStatus(dg);
|
||||||
|
return { oldTxState, newTxState };
|
||||||
|
});
|
||||||
|
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
return OperationAttemptResult.finishedEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a deposit group that is not in its final state yet.
|
||||||
|
*/
|
||||||
|
export async function processDepositGroup(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
depositGroupId: string,
|
||||||
|
options: {
|
||||||
|
cancellationToken?: CancellationToken;
|
||||||
|
} = {},
|
||||||
|
): Promise<OperationAttemptResult> {
|
||||||
|
const depositGroup = await ws.db
|
||||||
|
.mktx((x) => [x.depositGroups])
|
||||||
|
.runReadOnly(async (tx) => {
|
||||||
|
return tx.depositGroups.get(depositGroupId);
|
||||||
|
});
|
||||||
|
if (!depositGroup) {
|
||||||
|
logger.warn(`deposit group ${depositGroupId} not found`);
|
||||||
|
return OperationAttemptResult.finishedEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (depositGroup.operationStatus) {
|
||||||
|
case DepositOperationStatus.PendingTrack:
|
||||||
|
return processDepositGroupPendingTrack(
|
||||||
|
ws,
|
||||||
|
depositGroup,
|
||||||
|
options.cancellationToken,
|
||||||
|
);
|
||||||
|
case DepositOperationStatus.PendingKyc:
|
||||||
|
return processDepositGroupPendingKyc(ws, depositGroup);
|
||||||
|
case DepositOperationStatus.PendingDeposit:
|
||||||
|
return processDepositGroupPendingDeposit(
|
||||||
|
ws,
|
||||||
|
depositGroup,
|
||||||
|
options.cancellationToken,
|
||||||
|
);
|
||||||
|
case DepositOperationStatus.Aborting:
|
||||||
|
return processDepositGroupAborting(ws, depositGroup);
|
||||||
|
}
|
||||||
|
|
||||||
return OperationAttemptResult.finishedEmpty();
|
return OperationAttemptResult.finishedEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -928,16 +1075,17 @@ async function getExchangeWireFee(
|
|||||||
async function trackDeposit(
|
async function trackDeposit(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
depositGroup: DepositGroupRecord,
|
depositGroup: DepositGroupRecord,
|
||||||
dp: CoinDepositPermission,
|
coinPub: string,
|
||||||
|
exchangeUrl: string,
|
||||||
): Promise<TrackTransaction> {
|
): Promise<TrackTransaction> {
|
||||||
const wireHash = depositGroup.contractTermsRaw.h_wire;
|
const wireHash = depositGroup.contractTermsRaw.h_wire;
|
||||||
|
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`,
|
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${coinPub}`,
|
||||||
dp.exchange_url,
|
exchangeUrl,
|
||||||
);
|
);
|
||||||
const sigResp = await ws.cryptoApi.signTrackTransaction({
|
const sigResp = await ws.cryptoApi.signTrackTransaction({
|
||||||
coinPub: dp.coin_pub,
|
coinPub,
|
||||||
contractTermsHash: depositGroup.contractTermsHash,
|
contractTermsHash: depositGroup.contractTermsHash,
|
||||||
merchantPriv: depositGroup.merchantPriv,
|
merchantPriv: depositGroup.merchantPriv,
|
||||||
merchantPub: depositGroup.merchantPub,
|
merchantPub: depositGroup.merchantPub,
|
||||||
@ -1224,7 +1372,7 @@ export async function createDepositGroup(
|
|||||||
payto_uri: req.depositPaytoUri,
|
payto_uri: req.depositPaytoUri,
|
||||||
salt: wireSalt,
|
salt: wireSalt,
|
||||||
},
|
},
|
||||||
operationStatus: DepositOperationStatus.Pending,
|
operationStatus: DepositOperationStatus.PendingDeposit,
|
||||||
};
|
};
|
||||||
|
|
||||||
const transactionId = constructTransactionIdentifier({
|
const transactionId = constructTransactionIdentifier({
|
||||||
@ -1263,6 +1411,10 @@ export async function createDepositGroup(
|
|||||||
newTxState,
|
newTxState,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ws.notify({
|
||||||
|
type: NotificationType.BalanceChange,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
depositGroupId,
|
depositGroupId,
|
||||||
transactionId,
|
transactionId,
|
||||||
@ -1332,7 +1484,7 @@ export async function getCounterpartyEffectiveDepositAmount(
|
|||||||
* Get the fee amount that will be charged when trying to deposit the
|
* Get the fee amount that will be charged when trying to deposit the
|
||||||
* specified amount using the selected coins and the wire method.
|
* specified amount using the selected coins and the wire method.
|
||||||
*/
|
*/
|
||||||
export async function getTotalFeesForDepositAmount(
|
async function getTotalFeesForDepositAmount(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
wireType: string,
|
wireType: string,
|
||||||
total: AmountJson,
|
total: AmountJson,
|
||||||
|
@ -427,7 +427,7 @@ async function handlePendingMerge(
|
|||||||
const respJson = await mergeHttpResp.json();
|
const respJson = await mergeHttpResp.json();
|
||||||
const kycPending = codecForWalletKycUuid().decode(respJson);
|
const kycPending = codecForWalletKycUuid().decode(respJson);
|
||||||
logger.info(`kyc uuid response: ${j2s(kycPending)}`);
|
logger.info(`kyc uuid response: ${j2s(kycPending)}`);
|
||||||
processPeerPushCreditKycRequired(ws, peerInc, kycPending);
|
return processPeerPushCreditKycRequired(ws, peerInc, kycPending);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace(`merge request: ${j2s(mergeReq)}`);
|
logger.trace(`merge request: ${j2s(mergeReq)}`);
|
||||||
|
@ -32,8 +32,8 @@ import {
|
|||||||
PeerPushPaymentIncomingStatus,
|
PeerPushPaymentIncomingStatus,
|
||||||
PeerPullPaymentInitiationStatus,
|
PeerPullPaymentInitiationStatus,
|
||||||
WithdrawalGroupStatus,
|
WithdrawalGroupStatus,
|
||||||
DepositGroupOperationStatus,
|
|
||||||
TipRecordStatus,
|
TipRecordStatus,
|
||||||
|
DepositOperationStatus,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
import {
|
import {
|
||||||
PendingOperationsResponse,
|
PendingOperationsResponse,
|
||||||
@ -198,8 +198,8 @@ async function gatherDepositPending(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const dgs = await tx.depositGroups.indexes.byStatus.getAll(
|
const dgs = await tx.depositGroups.indexes.byStatus.getAll(
|
||||||
GlobalIDB.KeyRange.bound(
|
GlobalIDB.KeyRange.bound(
|
||||||
DepositGroupOperationStatus.Pending,
|
DepositOperationStatus.PendingDeposit,
|
||||||
DepositGroupOperationStatus.AbortingWithRefresh,
|
DepositOperationStatus.PendingKyc,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
for (const dg of dgs) {
|
for (const dg of dgs) {
|
||||||
|
@ -1108,14 +1108,6 @@ async function processPlanchetVerifyAndStoreCoin(
|
|||||||
|
|
||||||
wgContext.planchetsFinished.add(planchet.coinPub);
|
wgContext.planchetsFinished.add(planchet.coinPub);
|
||||||
|
|
||||||
// We create the notification here, as the async transaction below
|
|
||||||
// allows other planchet withdrawals to change wgContext.planchetsFinished
|
|
||||||
const notification: WalletNotification = {
|
|
||||||
type: NotificationType.CoinWithdrawn,
|
|
||||||
numTotal: wgContext.numPlanchets,
|
|
||||||
numWithdrawn: wgContext.planchetsFinished.size,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if this is the first time that the whole
|
// Check if this is the first time that the whole
|
||||||
// withdrawal succeeded. If so, mark the withdrawal
|
// withdrawal succeeded. If so, mark the withdrawal
|
||||||
// group as finished.
|
// group as finished.
|
||||||
@ -1138,9 +1130,7 @@ async function processPlanchetVerifyAndStoreCoin(
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (firstSuccess) {
|
ws.notify({ type: NotificationType.BalanceChange });
|
||||||
ws.notify(notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user