diff --git a/src/server/routes/transaction/cancel.ts b/src/server/routes/transaction/cancel.ts index 70e794f66..214eefcc3 100644 --- a/src/server/routes/transaction/cancel.ts +++ b/src/server/routes/transaction/cancel.ts @@ -40,7 +40,7 @@ export const responseBodySchema = Type.Object({ responseBodySchema.example = { result: { - qeueuId: "a20ed4ce-301d-4251-a7af-86bd88f6c015", + queueId: "a20ed4ce-301d-4251-a7af-86bd88f6c015", status: "success", }, }; diff --git a/src/server/utils/transaction.ts b/src/server/utils/transaction.ts index 1db9e3395..0407567cf 100644 --- a/src/server/utils/transaction.ts +++ b/src/server/utils/transaction.ts @@ -1,4 +1,3 @@ -import { TransactionResponse } from "@ethersproject/abstract-provider"; import { getDefaultGasOverrides } from "@thirdweb-dev/sdk"; import { StatusCodes } from "http-status-codes"; import { getTxById } from "../../db/transactions/getTxById"; @@ -25,10 +24,6 @@ export const cancelTransactionAndUpdate = async ({ }; } - let message = ""; - let error = null; - let transferTransactionResult: TransactionResponse | null = null; - if (txData.signerAddress && txData.accountAddress) { switch (txData.status) { case TransactionStatus.Errored: @@ -62,25 +57,44 @@ export const cancelTransactionAndUpdate = async ({ status: TransactionStatus.Cancelled, }, }); - message = "Transaction cancelled on-database successfully."; - break; + return { + message: "Transaction cancelled on-database successfully.", + }; } } else { switch (txData.status) { - case TransactionStatus.Errored: - error = createCustomError( + case TransactionStatus.Errored: { + if (txData.chainId && txData.fromAddress && txData.nonce) { + const { message, transactionHash } = await sendNullTransaction({ + chainId: parseInt(txData.chainId), + walletAddress: txData.fromAddress, + nonce: txData.nonce, + }); + if (transactionHash) { + await updateTx({ + queueId, + pgtx, + data: { + status: TransactionStatus.Cancelled, + }, + }); + } + + return { message, transactionHash }; + } + + throw createCustomError( `Transaction has already errored: ${txData.errorMessage}`, StatusCodes.BAD_REQUEST, "TransactionErrored", ); - break; + } case TransactionStatus.Cancelled: - error = createCustomError( + throw createCustomError( "Transaction is already cancelled.", StatusCodes.BAD_REQUEST, "TransactionAlreadyCancelled", ); - break; case TransactionStatus.Queued: await updateTx({ queueId, @@ -89,61 +103,79 @@ export const cancelTransactionAndUpdate = async ({ status: TransactionStatus.Cancelled, }, }); - message = "Transaction cancelled successfully."; - break; + return { + message: "Transaction cancelled successfully.", + }; case TransactionStatus.Mined: - error = createCustomError( + throw createCustomError( "Transaction already mined.", StatusCodes.BAD_REQUEST, "TransactionAlreadyMined", ); - break; case TransactionStatus.Sent: { - const sdk = await getSdk({ - chainId: parseInt(txData.chainId!), - walletAddress: txData.fromAddress!, - }); + if (txData.chainId && txData.fromAddress && txData.nonce) { + const { message, transactionHash } = await sendNullTransaction({ + chainId: parseInt(txData.chainId), + walletAddress: txData.fromAddress, + nonce: txData.nonce, + }); + if (transactionHash) { + await updateTx({ + queueId, + pgtx, + data: { + status: TransactionStatus.Cancelled, + }, + }); + } - const txReceipt = await sdk - .getProvider() - .getTransactionReceipt(txData.transactionHash!); - if (txReceipt) { - message = "Transaction already mined."; - break; + return { message, transactionHash }; } + } + } + } - const gasOverrides = await getDefaultGasOverrides(sdk.getProvider()); - transferTransactionResult = await sdk.wallet.sendRawTransaction({ - to: txData.fromAddress!, - from: txData.fromAddress!, - data: "0x", - value: "0x00", - nonce: txData.nonce!, - ...multiplyGasOverrides(gasOverrides, 2), - }); + throw new Error("Unhandled cancellation state."); +}; - message = "Transaction cancelled successfully."; +const sendNullTransaction = async (args: { + chainId: number; + walletAddress: string; + nonce: number; + transactionHash?: string; +}): Promise<{ + message: string; + transactionHash?: string; +}> => { + const { chainId, walletAddress, nonce, transactionHash } = args; - await updateTx({ - queueId, - pgtx, - data: { - status: TransactionStatus.Cancelled, - }, - }); - break; - } - default: - break; + const sdk = await getSdk({ chainId, walletAddress }); + const provider = sdk.getProvider(); + + // Skip if the transaction is already mined. + if (transactionHash) { + const receipt = await provider.getTransactionReceipt(transactionHash); + if (receipt) { + return { message: "Transaction already mined." }; } } - if (error) { - throw error; - } + try { + const gasOverrides = await getDefaultGasOverrides(provider); + const { hash } = await sdk.wallet.sendRawTransaction({ + to: walletAddress, + from: walletAddress, + data: "0x", + value: "0", + nonce, + ...multiplyGasOverrides(gasOverrides, 2), + }); - return { - message, - transactionHash: transferTransactionResult?.hash, - }; + return { + message: "Transaction cancelled successfully.", + transactionHash: hash, + }; + } catch (e: any) { + return { message: e.toString() }; + } };