Skip to content

Commit 3068b1d

Browse files
authored
chore: Allow cancelling txs that already errored (#491)
* chore: Allow cancelling txs that already errored * clean up response * naming * update cancelledAt
1 parent b703dd3 commit 3068b1d

File tree

2 files changed

+87
-55
lines changed

2 files changed

+87
-55
lines changed

src/server/routes/transaction/cancel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const responseBodySchema = Type.Object({
4040

4141
responseBodySchema.example = {
4242
result: {
43-
qeueuId: "a20ed4ce-301d-4251-a7af-86bd88f6c015",
43+
queueId: "a20ed4ce-301d-4251-a7af-86bd88f6c015",
4444
status: "success",
4545
},
4646
};

src/server/utils/transaction.ts

Lines changed: 86 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { TransactionResponse } from "@ethersproject/abstract-provider";
21
import { getDefaultGasOverrides } from "@thirdweb-dev/sdk";
32
import { StatusCodes } from "http-status-codes";
43
import { getTxById } from "../../db/transactions/getTxById";
@@ -25,10 +24,6 @@ export const cancelTransactionAndUpdate = async ({
2524
};
2625
}
2726

28-
let message = "";
29-
let error = null;
30-
let transferTransactionResult: TransactionResponse | null = null;
31-
3227
if (txData.signerAddress && txData.accountAddress) {
3328
switch (txData.status) {
3429
case TransactionStatus.Errored:
@@ -62,25 +57,44 @@ export const cancelTransactionAndUpdate = async ({
6257
status: TransactionStatus.Cancelled,
6358
},
6459
});
65-
message = "Transaction cancelled on-database successfully.";
66-
break;
60+
return {
61+
message: "Transaction cancelled on-database successfully.",
62+
};
6763
}
6864
} else {
6965
switch (txData.status) {
70-
case TransactionStatus.Errored:
71-
error = createCustomError(
66+
case TransactionStatus.Errored: {
67+
if (txData.chainId && txData.fromAddress && txData.nonce) {
68+
const { message, transactionHash } = await sendNullTransaction({
69+
chainId: parseInt(txData.chainId),
70+
walletAddress: txData.fromAddress,
71+
nonce: txData.nonce,
72+
});
73+
if (transactionHash) {
74+
await updateTx({
75+
queueId,
76+
pgtx,
77+
data: {
78+
status: TransactionStatus.Cancelled,
79+
},
80+
});
81+
}
82+
83+
return { message, transactionHash };
84+
}
85+
86+
throw createCustomError(
7287
`Transaction has already errored: ${txData.errorMessage}`,
7388
StatusCodes.BAD_REQUEST,
7489
"TransactionErrored",
7590
);
76-
break;
91+
}
7792
case TransactionStatus.Cancelled:
78-
error = createCustomError(
93+
throw createCustomError(
7994
"Transaction is already cancelled.",
8095
StatusCodes.BAD_REQUEST,
8196
"TransactionAlreadyCancelled",
8297
);
83-
break;
8498
case TransactionStatus.Queued:
8599
await updateTx({
86100
queueId,
@@ -89,61 +103,79 @@ export const cancelTransactionAndUpdate = async ({
89103
status: TransactionStatus.Cancelled,
90104
},
91105
});
92-
message = "Transaction cancelled successfully.";
93-
break;
106+
return {
107+
message: "Transaction cancelled successfully.",
108+
};
94109
case TransactionStatus.Mined:
95-
error = createCustomError(
110+
throw createCustomError(
96111
"Transaction already mined.",
97112
StatusCodes.BAD_REQUEST,
98113
"TransactionAlreadyMined",
99114
);
100-
break;
101115
case TransactionStatus.Sent: {
102-
const sdk = await getSdk({
103-
chainId: parseInt(txData.chainId!),
104-
walletAddress: txData.fromAddress!,
105-
});
116+
if (txData.chainId && txData.fromAddress && txData.nonce) {
117+
const { message, transactionHash } = await sendNullTransaction({
118+
chainId: parseInt(txData.chainId),
119+
walletAddress: txData.fromAddress,
120+
nonce: txData.nonce,
121+
});
122+
if (transactionHash) {
123+
await updateTx({
124+
queueId,
125+
pgtx,
126+
data: {
127+
status: TransactionStatus.Cancelled,
128+
},
129+
});
130+
}
106131

107-
const txReceipt = await sdk
108-
.getProvider()
109-
.getTransactionReceipt(txData.transactionHash!);
110-
if (txReceipt) {
111-
message = "Transaction already mined.";
112-
break;
132+
return { message, transactionHash };
113133
}
134+
}
135+
}
136+
}
114137

115-
const gasOverrides = await getDefaultGasOverrides(sdk.getProvider());
116-
transferTransactionResult = await sdk.wallet.sendRawTransaction({
117-
to: txData.fromAddress!,
118-
from: txData.fromAddress!,
119-
data: "0x",
120-
value: "0x00",
121-
nonce: txData.nonce!,
122-
...multiplyGasOverrides(gasOverrides, 2),
123-
});
138+
throw new Error("Unhandled cancellation state.");
139+
};
124140

125-
message = "Transaction cancelled successfully.";
141+
const sendNullTransaction = async (args: {
142+
chainId: number;
143+
walletAddress: string;
144+
nonce: number;
145+
transactionHash?: string;
146+
}): Promise<{
147+
message: string;
148+
transactionHash?: string;
149+
}> => {
150+
const { chainId, walletAddress, nonce, transactionHash } = args;
126151

127-
await updateTx({
128-
queueId,
129-
pgtx,
130-
data: {
131-
status: TransactionStatus.Cancelled,
132-
},
133-
});
134-
break;
135-
}
136-
default:
137-
break;
152+
const sdk = await getSdk({ chainId, walletAddress });
153+
const provider = sdk.getProvider();
154+
155+
// Skip if the transaction is already mined.
156+
if (transactionHash) {
157+
const receipt = await provider.getTransactionReceipt(transactionHash);
158+
if (receipt) {
159+
return { message: "Transaction already mined." };
138160
}
139161
}
140162

141-
if (error) {
142-
throw error;
143-
}
163+
try {
164+
const gasOverrides = await getDefaultGasOverrides(provider);
165+
const { hash } = await sdk.wallet.sendRawTransaction({
166+
to: walletAddress,
167+
from: walletAddress,
168+
data: "0x",
169+
value: "0",
170+
nonce,
171+
...multiplyGasOverrides(gasOverrides, 2),
172+
});
144173

145-
return {
146-
message,
147-
transactionHash: transferTransactionResult?.hash,
148-
};
174+
return {
175+
message: "Transaction cancelled successfully.",
176+
transactionHash: hash,
177+
};
178+
} catch (e: any) {
179+
return { message: e.toString() };
180+
}
149181
};

0 commit comments

Comments
 (0)