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
|
// Placeholder until D37 is fully implemented
|
||||||
Unknown = "unknown",
|
Unknown = "unknown",
|
||||||
Deposit = "deposit",
|
Deposit = "deposit",
|
||||||
KycRequired = "kyc-required",
|
KycRequired = "kyc",
|
||||||
|
AmlRequired = "aml",
|
||||||
Track = "track",
|
Track = "track",
|
||||||
Refresh = "refresh",
|
Refresh = "refresh",
|
||||||
Pickup = "pickup",
|
Pickup = "pickup",
|
||||||
@ -111,6 +112,7 @@ export enum TransactionMinorState {
|
|||||||
BankConfirmTransfer = "bank-confirm-transfer",
|
BankConfirmTransfer = "bank-confirm-transfer",
|
||||||
WithdrawCoins = "withdraw-coins",
|
WithdrawCoins = "withdraw-coins",
|
||||||
ExchangeWaitReserve = "exchange-wait-reserve",
|
ExchangeWaitReserve = "exchange-wait-reserve",
|
||||||
|
AbortingBank = "aborting-bank",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionsResponse {
|
export interface TransactionsResponse {
|
||||||
|
@ -171,6 +171,16 @@ export enum WithdrawalGroupStatus {
|
|||||||
*/
|
*/
|
||||||
AbortingBank = 14,
|
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.
|
* The corresponding withdraw record has been created.
|
||||||
* No further processing is done, unless explicitly requested
|
* No further processing is done, unless explicitly requested
|
||||||
@ -187,7 +197,10 @@ export enum WithdrawalGroupStatus {
|
|||||||
SuspendedWaitConfirmBank = 53,
|
SuspendedWaitConfirmBank = 53,
|
||||||
SuspendedQueryingStatus = 54,
|
SuspendedQueryingStatus = 54,
|
||||||
SuspendedReady = 55,
|
SuspendedReady = 55,
|
||||||
SuspendedAbortingBank = 55,
|
SuspendedAbortingBank = 56,
|
||||||
|
SuspendedKyc = 57,
|
||||||
|
SuspendedAml = 58,
|
||||||
|
FailedAbortingBank = 59,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,6 +112,7 @@ import {
|
|||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
TaskIdentifiers,
|
TaskIdentifiers,
|
||||||
|
constructTaskIdentifier,
|
||||||
} from "../util/retries.js";
|
} from "../util/retries.js";
|
||||||
import {
|
import {
|
||||||
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
||||||
@ -128,13 +129,284 @@ import {
|
|||||||
selectForcedWithdrawalDenominations,
|
selectForcedWithdrawalDenominations,
|
||||||
selectWithdrawalDenominations,
|
selectWithdrawalDenominations,
|
||||||
} from "../util/coinSelection.js";
|
} 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.
|
* Logger for this file.
|
||||||
*/
|
*/
|
||||||
const logger = new Logger("operations/withdraw.ts");
|
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(
|
export function computeWithdrawalTransactionStatus(
|
||||||
wgRecord: WithdrawalGroupRecord,
|
wgRecord: WithdrawalGroupRecord,
|
||||||
): TransactionState {
|
): TransactionState {
|
||||||
@ -192,6 +464,41 @@ export function computeWithdrawalTransactionStatus(
|
|||||||
major: TransactionMajorState.Suspended,
|
major: TransactionMajorState.Suspended,
|
||||||
minor: TransactionMinorState.BankConfirmTransfer,
|
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