From 8d37d3b41058a9fab8ba76adf22c15346bf0be74 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Wed, 10 Apr 2024 11:00:18 -0700 Subject: [PATCH 1/4] chore: Allow cancelling txs that already errored --- src/server/routes/transaction/cancel.ts | 2 +- src/server/utils/transaction.ts | 126 +++++++++++++++--------- 2 files changed, 81 insertions(+), 47 deletions(-) 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..3ce936805 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) { + throw new Error("Invalid transaction state to cancel."); + } + if (txData.nonce) { + const { transactionHash, error } = await sendNullTransaction({ + chainId: parseInt(txData.chainId), + walletAddress: txData.fromAddress, + nonce: txData.nonce, + }); + if (error) { + return { message: error }; + } + + return { + message: "Transaction cancelled successfully.", + 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,40 +103,28 @@ 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!, - }); - - const txReceipt = await sdk - .getProvider() - .getTransactionReceipt(txData.transactionHash!); - if (txReceipt) { - message = "Transaction already mined."; - break; + if (!txData.chainId || !txData.fromAddress || !txData.nonce) { + throw new Error("Invalid transaction state to cancel."); } - 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), + const { transactionHash, error } = await sendNullTransaction({ + chainId: parseInt(txData.chainId), + walletAddress: txData.fromAddress, + nonce: txData.nonce, }); - - message = "Transaction cancelled successfully."; + if (error) { + return { message: error }; + } await updateTx({ queueId, @@ -131,19 +133,51 @@ export const cancelTransactionAndUpdate = async ({ status: TransactionStatus.Cancelled, }, }); - break; + return { + message: "Transaction cancelled successfully.", + transactionHash, + }; } - default: - break; } } - if (error) { - throw error; + throw new Error("Unhandled cancellation state."); +}; + +const sendNullTransaction = async (args: { + chainId: number; + walletAddress: string; + nonce: number; + transactionHash?: string; +}): Promise<{ + transactionHash?: string; + error?: string; +}> => { + const { chainId, walletAddress, nonce, transactionHash } = args; + + const sdk = await getSdk({ chainId, walletAddress }); + const provider = sdk.getProvider(); + + // Skip if the tx is already mined. + if (transactionHash) { + const txReceipt = await provider.getTransactionReceipt(transactionHash); + if (txReceipt) { + return { error: "Transaction already mined." }; + } } - return { - message, - transactionHash: transferTransactionResult?.hash, - }; + 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 { transactionHash: hash }; + } catch (e: any) { + return { error: e.toString() }; + } }; From e4e4be97c559944063a559fcc06e3234d1214310 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Wed, 10 Apr 2024 11:06:23 -0700 Subject: [PATCH 2/4] clean up response --- src/server/utils/transaction.ts | 64 +++++++++++++-------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/src/server/utils/transaction.ts b/src/server/utils/transaction.ts index 3ce936805..c6f770137 100644 --- a/src/server/utils/transaction.ts +++ b/src/server/utils/transaction.ts @@ -64,23 +64,12 @@ export const cancelTransactionAndUpdate = async ({ } else { switch (txData.status) { case TransactionStatus.Errored: { - if (!txData.chainId || !txData.fromAddress) { - throw new Error("Invalid transaction state to cancel."); - } - if (txData.nonce) { - const { transactionHash, error } = await sendNullTransaction({ + if (txData.chainId && txData.fromAddress && txData.nonce) { + return await sendNullTransaction({ chainId: parseInt(txData.chainId), walletAddress: txData.fromAddress, nonce: txData.nonce, }); - if (error) { - return { message: error }; - } - - return { - message: "Transaction cancelled successfully.", - transactionHash, - }; } throw createCustomError( @@ -113,30 +102,24 @@ export const cancelTransactionAndUpdate = async ({ "TransactionAlreadyMined", ); case TransactionStatus.Sent: { - if (!txData.chainId || !txData.fromAddress || !txData.nonce) { - throw new Error("Invalid transaction state to cancel."); - } + if (txData.chainId && txData.fromAddress && txData.nonce) { + const { transactionHash, message } = await sendNullTransaction({ + chainId: parseInt(txData.chainId), + walletAddress: txData.fromAddress, + nonce: txData.nonce, + }); + if (transactionHash) { + await updateTx({ + queueId, + pgtx, + data: { + status: TransactionStatus.Cancelled, + }, + }); + } - const { transactionHash, error } = await sendNullTransaction({ - chainId: parseInt(txData.chainId), - walletAddress: txData.fromAddress, - nonce: txData.nonce, - }); - if (error) { - return { message: error }; + return { message, transactionHash }; } - - await updateTx({ - queueId, - pgtx, - data: { - status: TransactionStatus.Cancelled, - }, - }); - return { - message: "Transaction cancelled successfully.", - transactionHash, - }; } } } @@ -150,8 +133,8 @@ const sendNullTransaction = async (args: { nonce: number; transactionHash?: string; }): Promise<{ + message: string; transactionHash?: string; - error?: string; }> => { const { chainId, walletAddress, nonce, transactionHash } = args; @@ -162,7 +145,7 @@ const sendNullTransaction = async (args: { if (transactionHash) { const txReceipt = await provider.getTransactionReceipt(transactionHash); if (txReceipt) { - return { error: "Transaction already mined." }; + return { message: "Transaction already mined." }; } } @@ -176,8 +159,11 @@ const sendNullTransaction = async (args: { nonce, ...multiplyGasOverrides(gasOverrides, 2), }); - return { transactionHash: hash }; + return { + message: "Transaction cancelled successfully.", + transactionHash: hash, + }; } catch (e: any) { - return { error: e.toString() }; + return { message: e.toString() }; } }; From 15a24bb6414d230e7d9ca2adf5d0686fa3e03bfb Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Wed, 10 Apr 2024 11:07:39 -0700 Subject: [PATCH 3/4] naming --- src/server/utils/transaction.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/utils/transaction.ts b/src/server/utils/transaction.ts index c6f770137..f31fce814 100644 --- a/src/server/utils/transaction.ts +++ b/src/server/utils/transaction.ts @@ -141,10 +141,10 @@ const sendNullTransaction = async (args: { const sdk = await getSdk({ chainId, walletAddress }); const provider = sdk.getProvider(); - // Skip if the tx is already mined. + // Skip if the transaction is already mined. if (transactionHash) { - const txReceipt = await provider.getTransactionReceipt(transactionHash); - if (txReceipt) { + const receipt = await provider.getTransactionReceipt(transactionHash); + if (receipt) { return { message: "Transaction already mined." }; } } From 32ba4e3fe828df2c20f6a56654f2fb7a7181f58a Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Wed, 10 Apr 2024 11:12:46 -0700 Subject: [PATCH 4/4] update cancelledAt --- src/server/utils/transaction.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/server/utils/transaction.ts b/src/server/utils/transaction.ts index f31fce814..0407567cf 100644 --- a/src/server/utils/transaction.ts +++ b/src/server/utils/transaction.ts @@ -65,11 +65,22 @@ export const cancelTransactionAndUpdate = async ({ switch (txData.status) { case TransactionStatus.Errored: { if (txData.chainId && txData.fromAddress && txData.nonce) { - return await sendNullTransaction({ + 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( @@ -103,7 +114,7 @@ export const cancelTransactionAndUpdate = async ({ ); case TransactionStatus.Sent: { if (txData.chainId && txData.fromAddress && txData.nonce) { - const { transactionHash, message } = await sendNullTransaction({ + const { message, transactionHash } = await sendNullTransaction({ chainId: parseInt(txData.chainId), walletAddress: txData.fromAddress, nonce: txData.nonce, @@ -159,6 +170,7 @@ const sendNullTransaction = async (args: { nonce, ...multiplyGasOverrides(gasOverrides, 2), }); + return { message: "Transaction cancelled successfully.", transactionHash: hash,