Skip to content

chore: Allow cancelling txs that already errored #491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/server/routes/transaction/cancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
};
Expand Down
140 changes: 86 additions & 54 deletions src/server/utils/transaction.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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() };
}
};