wallet-core: implement withdrawal tx transitions
This commit is contained in:
parent
7c04fbd806
commit
c4f5c83b8e
@ -101,7 +101,8 @@ export enum TransactionMinorState {
|
||||
// Placeholder until D37 is fully implemented
|
||||
Unknown = "unknown",
|
||||
Deposit = "deposit",
|
||||
KycRequired = "kyc-required",
|
||||
KycRequired = "kyc",
|
||||
AmlRequired = "aml",
|
||||
Track = "track",
|
||||
Refresh = "refresh",
|
||||
Pickup = "pickup",
|
||||
@ -111,6 +112,7 @@ export enum TransactionMinorState {
|
||||
BankConfirmTransfer = "bank-confirm-transfer",
|
||||
WithdrawCoins = "withdraw-coins",
|
||||
ExchangeWaitReserve = "exchange-wait-reserve",
|
||||
AbortingBank = "aborting-bank",
|
||||
}
|
||||
|
||||
export interface TransactionsResponse {
|
||||
|
@ -171,6 +171,16 @@ export enum WithdrawalGroupStatus {
|
||||
*/
|
||||
AbortingBank = 14,
|
||||
|
||||
/**
|
||||
* Exchange wants KYC info from the user.
|
||||
*/
|
||||
Kyc = 16,
|
||||
|
||||
/**
|
||||
* Exchange is doing AML checks.
|
||||
*/
|
||||
Aml = 17,
|
||||
|
||||
/**
|
||||
* The corresponding withdraw record has been created.
|
||||
* No further processing is done, unless explicitly requested
|
||||
@ -187,7 +197,10 @@ export enum WithdrawalGroupStatus {
|
||||
SuspendedWaitConfirmBank = 53,
|
||||
SuspendedQueryingStatus = 54,
|
||||
SuspendedReady = 55,
|
||||
SuspendedAbortingBank = 55,
|
||||
SuspendedAbortingBank = 56,
|
||||
SuspendedKyc = 57,
|
||||
SuspendedAml = 58,
|
||||
FailedAbortingBank = 59,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,6 +112,7 @@ import {
|
||||
OperationAttemptResult,
|
||||
OperationAttemptResultType,
|
||||
TaskIdentifiers,
|
||||
constructTaskIdentifier,
|
||||
} from "../util/retries.js";
|
||||
import {
|
||||
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
||||
@ -128,13 +129,284 @@ import {
|
||||
selectForcedWithdrawalDenominations,
|
||||
selectWithdrawalDenominations,
|
||||
} from "../util/coinSelection.js";
|
||||
import { isWithdrawableDenom } from "../index.js";
|
||||
import { PendingTaskType, isWithdrawableDenom } from "../index.js";
|
||||
import {
|
||||
constructTransactionIdentifier,
|
||||
stopLongpolling,
|
||||
} from "./transactions.js";
|
||||
|
||||
/**
|
||||
* Logger for this file.
|
||||
*/
|
||||
const logger = new Logger("operations/withdraw.ts");
|
||||
|
||||
export async function suspendWithdrawalTransaction(
|
||||
ws: InternalWalletState,
|
||||
withdrawalGroupId: string,
|
||||
) {
|
||||
const taskId = constructTaskIdentifier({
|
||||
tag: PendingTaskType.Withdraw,
|
||||
withdrawalGroupId,
|
||||
});
|
||||
stopLongpolling(ws, taskId);
|
||||
const stateUpdate = await ws.db
|
||||
.mktx((x) => [x.withdrawalGroups])
|
||||
.runReadWrite(async (tx) => {
|
||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||
if (!wg) {
|
||||
logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
|
||||
return;
|
||||
}
|
||||
let newStatus: WithdrawalGroupStatus | undefined = undefined;
|
||||
switch (wg.status) {
|
||||
case WithdrawalGroupStatus.Ready:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedReady;
|
||||
break;
|
||||
case WithdrawalGroupStatus.AbortingBank:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedAbortingBank;
|
||||
break;
|
||||
case WithdrawalGroupStatus.WaitConfirmBank:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedWaitConfirmBank;
|
||||
break;
|
||||
case WithdrawalGroupStatus.RegisteringBank:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedRegisteringBank;
|
||||
break;
|
||||
case WithdrawalGroupStatus.QueryingStatus:
|
||||
newStatus = WithdrawalGroupStatus.QueryingStatus;
|
||||
break;
|
||||
case WithdrawalGroupStatus.Kyc:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedKyc;
|
||||
break;
|
||||
case WithdrawalGroupStatus.Aml:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedAml;
|
||||
break;
|
||||
default:
|
||||
logger.warn(
|
||||
`Unsupported 'suspend' on withdrawal transaction in status ${wg.status}`,
|
||||
);
|
||||
}
|
||||
if (newStatus != null) {
|
||||
const oldTxState = computeWithdrawalTransactionStatus(wg);
|
||||
wg.status = newStatus;
|
||||
const newTxState = computeWithdrawalTransactionStatus(wg);
|
||||
await tx.withdrawalGroups.put(wg);
|
||||
return {
|
||||
oldTxState,
|
||||
newTxState,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
if (stateUpdate) {
|
||||
ws.notify({
|
||||
type: NotificationType.TransactionStateTransition,
|
||||
transactionId: constructTransactionIdentifier({
|
||||
tag: TransactionType.Withdrawal,
|
||||
withdrawalGroupId,
|
||||
}),
|
||||
oldTxState: stateUpdate.oldTxState,
|
||||
newTxState: stateUpdate.newTxState,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function resumeWithdrawalTransaction(
|
||||
ws: InternalWalletState,
|
||||
withdrawalGroupId: string,
|
||||
) {
|
||||
const stateUpdate = await ws.db
|
||||
.mktx((x) => [x.withdrawalGroups])
|
||||
.runReadWrite(async (tx) => {
|
||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||
if (!wg) {
|
||||
logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
|
||||
return;
|
||||
}
|
||||
let newStatus: WithdrawalGroupStatus | undefined = undefined;
|
||||
switch (wg.status) {
|
||||
case WithdrawalGroupStatus.SuspendedReady:
|
||||
newStatus = WithdrawalGroupStatus.Ready;
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedAbortingBank:
|
||||
newStatus = WithdrawalGroupStatus.AbortingBank;
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
|
||||
newStatus = WithdrawalGroupStatus.WaitConfirmBank;
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedQueryingStatus:
|
||||
newStatus = WithdrawalGroupStatus.QueryingStatus;
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedRegisteringBank:
|
||||
newStatus = WithdrawalGroupStatus.RegisteringBank;
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedAml:
|
||||
newStatus = WithdrawalGroupStatus.Aml;
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedKyc:
|
||||
newStatus = WithdrawalGroupStatus.Kyc;
|
||||
break;
|
||||
default:
|
||||
logger.warn(
|
||||
`Unsupported 'resume' on withdrawal transaction in status ${wg.status}`,
|
||||
);
|
||||
}
|
||||
if (newStatus != null) {
|
||||
const oldTxState = computeWithdrawalTransactionStatus(wg);
|
||||
wg.status = newStatus;
|
||||
const newTxState = computeWithdrawalTransactionStatus(wg);
|
||||
await tx.withdrawalGroups.put(wg);
|
||||
return {
|
||||
oldTxState,
|
||||
newTxState,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
if (stateUpdate) {
|
||||
ws.notify({
|
||||
type: NotificationType.TransactionStateTransition,
|
||||
transactionId: constructTransactionIdentifier({
|
||||
tag: TransactionType.Withdrawal,
|
||||
withdrawalGroupId,
|
||||
}),
|
||||
oldTxState: stateUpdate.oldTxState,
|
||||
newTxState: stateUpdate.newTxState,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function abortWithdrawalTransaction(
|
||||
ws: InternalWalletState,
|
||||
withdrawalGroupId: string,
|
||||
) {
|
||||
const taskId = constructTaskIdentifier({
|
||||
tag: PendingTaskType.Withdraw,
|
||||
withdrawalGroupId,
|
||||
});
|
||||
stopLongpolling(ws, taskId);
|
||||
const stateUpdate = await ws.db
|
||||
.mktx((x) => [x.withdrawalGroups])
|
||||
.runReadWrite(async (tx) => {
|
||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||
if (!wg) {
|
||||
logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
|
||||
return;
|
||||
}
|
||||
let newStatus: WithdrawalGroupStatus | undefined = undefined;
|
||||
switch (wg.status) {
|
||||
case WithdrawalGroupStatus.WaitConfirmBank:
|
||||
case WithdrawalGroupStatus.RegisteringBank:
|
||||
case WithdrawalGroupStatus.AbortingBank:
|
||||
newStatus = WithdrawalGroupStatus.AbortingBank;
|
||||
break;
|
||||
case WithdrawalGroupStatus.Aml:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedAml;
|
||||
break;
|
||||
case WithdrawalGroupStatus.Kyc:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedKyc;
|
||||
break;
|
||||
case WithdrawalGroupStatus.QueryingStatus:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus;
|
||||
break;
|
||||
case WithdrawalGroupStatus.Ready:
|
||||
newStatus = WithdrawalGroupStatus.SuspendedReady;
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedAbortingBank:
|
||||
case WithdrawalGroupStatus.SuspendedQueryingStatus:
|
||||
case WithdrawalGroupStatus.SuspendedAml:
|
||||
case WithdrawalGroupStatus.SuspendedKyc:
|
||||
case WithdrawalGroupStatus.SuspendedReady:
|
||||
// No transition needed
|
||||
break;
|
||||
case WithdrawalGroupStatus.SuspendedRegisteringBank:
|
||||
case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
|
||||
case WithdrawalGroupStatus.Finished:
|
||||
case WithdrawalGroupStatus.BankAborted:
|
||||
// Not allowed
|
||||
break;
|
||||
}
|
||||
if (newStatus != null) {
|
||||
const oldTxState = computeWithdrawalTransactionStatus(wg);
|
||||
wg.status = newStatus;
|
||||
const newTxState = computeWithdrawalTransactionStatus(wg);
|
||||
await tx.withdrawalGroups.put(wg);
|
||||
return {
|
||||
oldTxState,
|
||||
newTxState,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
if (stateUpdate) {
|
||||
ws.notify({
|
||||
type: NotificationType.TransactionStateTransition,
|
||||
transactionId: constructTransactionIdentifier({
|
||||
tag: TransactionType.Withdrawal,
|
||||
withdrawalGroupId,
|
||||
}),
|
||||
oldTxState: stateUpdate.oldTxState,
|
||||
newTxState: stateUpdate.newTxState,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Called "cancel" in the spec right now,
|
||||
// from suspended-aborting.
|
||||
export async function cancelAbortingWithdrawalTransaction(
|
||||
ws: InternalWalletState,
|
||||
withdrawalGroupId: string,
|
||||
) {
|
||||
const taskId = constructTaskIdentifier({
|
||||
tag: PendingTaskType.Withdraw,
|
||||
withdrawalGroupId,
|
||||
});
|
||||
stopLongpolling(ws, taskId);
|
||||
const stateUpdate = await ws.db
|
||||
.mktx((x) => [x.withdrawalGroups])
|
||||
.runReadWrite(async (tx) => {
|
||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||
if (!wg) {
|
||||
logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
|
||||
return;
|
||||
}
|
||||
let newStatus: WithdrawalGroupStatus | undefined = undefined;
|
||||
switch (wg.status) {
|
||||
case WithdrawalGroupStatus.AbortingBank:
|
||||
newStatus = WithdrawalGroupStatus.FailedAbortingBank;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (newStatus != null) {
|
||||
const oldTxState = computeWithdrawalTransactionStatus(wg);
|
||||
wg.status = newStatus;
|
||||
const newTxState = computeWithdrawalTransactionStatus(wg);
|
||||
await tx.withdrawalGroups.put(wg);
|
||||
return {
|
||||
oldTxState,
|
||||
newTxState,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
if (stateUpdate) {
|
||||
ws.notify({
|
||||
type: NotificationType.TransactionStateTransition,
|
||||
transactionId: constructTransactionIdentifier({
|
||||
tag: TransactionType.Withdrawal,
|
||||
withdrawalGroupId,
|
||||
}),
|
||||
oldTxState: stateUpdate.oldTxState,
|
||||
newTxState: stateUpdate.newTxState,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function computeWithdrawalTransactionStatus(
|
||||
wgRecord: WithdrawalGroupRecord,
|
||||
): TransactionState {
|
||||
@ -192,6 +464,41 @@ export function computeWithdrawalTransactionStatus(
|
||||
major: TransactionMajorState.Suspended,
|
||||
minor: TransactionMinorState.BankConfirmTransfer,
|
||||
};
|
||||
case WithdrawalGroupStatus.SuspendedReady: {
|
||||
return {
|
||||
major: TransactionMajorState.Suspended,
|
||||
minor: TransactionMinorState.WithdrawCoins,
|
||||
};
|
||||
}
|
||||
case WithdrawalGroupStatus.Aml: {
|
||||
return {
|
||||
major: TransactionMajorState.Pending,
|
||||
minor: TransactionMinorState.AmlRequired,
|
||||
};
|
||||
}
|
||||
case WithdrawalGroupStatus.Kyc: {
|
||||
return {
|
||||
major: TransactionMajorState.Pending,
|
||||
minor: TransactionMinorState.KycRequired,
|
||||
};
|
||||
}
|
||||
case WithdrawalGroupStatus.SuspendedAml: {
|
||||
return {
|
||||
major: TransactionMajorState.Suspended,
|
||||
minor: TransactionMinorState.AmlRequired,
|
||||
};
|
||||
}
|
||||
case WithdrawalGroupStatus.SuspendedKyc: {
|
||||
return {
|
||||
major: TransactionMajorState.Suspended,
|
||||
minor: TransactionMinorState.KycRequired,
|
||||
};
|
||||
}
|
||||
case WithdrawalGroupStatus.FailedAbortingBank:
|
||||
return {
|
||||
major: TransactionMajorState.Failed,
|
||||
minor: TransactionMinorState.AbortingBank,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user