Skip to content
Merged
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
59 changes: 34 additions & 25 deletions src/server/routes/transaction/cancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,33 +82,39 @@ export async function cancelTransaction(fastify: FastifyInstance) {

const message = "Transaction successfully cancelled.";
let cancelledTransaction: CancelledTransaction | null = null;
if (!transaction.isUserOp) {
if (transaction.status === "queued") {
// Remove all retries from the SEND_TRANSACTION queue.
const config = await getConfig();
for (
let resendCount = 0;
resendCount < config.maxRetriesPerTx;
resendCount++
) {
await SendTransactionQueue.remove({ queueId, resendCount });
}
if (transaction.status === "queued") {
// Remove all retries from the SEND_TRANSACTION queue.
const config = await getConfig();
for (
let resendCount = 0;
resendCount < config.maxRetriesPerTx;
resendCount++
) {
await SendTransactionQueue.remove({ queueId, resendCount });
}

cancelledTransaction = {
...transaction,
status: "cancelled",
cancelledAt: new Date(),
cancelledTransaction = {
...transaction,
status: "cancelled",
cancelledAt: new Date(),

// Dummy data since the transaction was never sent.
sentAt: new Date(),
sentAtBlock: await getBlockNumberish(transaction.chainId),
// Dummy data since the transaction was never sent.
sentAt: new Date(),
sentAtBlock: await getBlockNumberish(transaction.chainId),

isUserOp: false,
gas: 0n,
nonce: -1,
sentTransactionHashes: [],
};
} else if (transaction.status === "sent") {
// isUserOp: false,
gas: 0n,
...(transaction.isUserOp
? {
userOpHash:
"0x0000000000000000000000000000000000000000000000000000000000000000",
nonce: "cancelled",
isUserOp: true,
}
: { nonce: -1, sentTransactionHashes: [], isUserOp: false }),
};
Comment on lines +96 to +115
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t overwrite nonce/gas and avoid dummy “sent” fields for queued cancels

Overwriting nonce with -1/"cancelled", forcing gas to 0n, and fabricating sentAt/sentAtBlock introduces data loss and distorts analytics/webhooks. Preserve original fields; avoid unnecessary RPC calls for queued items; and don’t inject a zero userOpHash.

         cancelledTransaction = {
           ...transaction,
           status: "cancelled",
           cancelledAt: new Date(),
-
-          // Dummy data since the transaction was never sent.
-          sentAt: new Date(),
-          sentAtBlock: await getBlockNumberish(transaction.chainId),
-
-          // isUserOp: false,
-          gas: 0n,
-          ...(transaction.isUserOp
-            ? {
-                userOpHash:
-                  "0x0000000000000000000000000000000000000000000000000000000000000000",
-                nonce: "cancelled",
-                isUserOp: true,
-              }
-            : { nonce: -1, sentTransactionHashes: [], isUserOp: false }),
+          // Preserve original nonce/gas; do not fabricate "sent" metadata.
+          ...(transaction.isUserOp
+            ? {
+                // keep existing userOp-specific fields if present; don't fabricate a hash
+                isUserOp: true,
+              }
+            : {
+                isUserOp: false,
+                // keep any existing hashes array if present
+                sentTransactionHashes: transaction.sentTransactionHashes ?? [],
+              }),
         };

Notes:

  • If CancelledTransaction currently requires sentAt/sentAtBlock, consider making them optional or handle “never sent” in downstream analytics instead of fabricating values. I can help adjust the type and usages.
🤖 Prompt for AI Agents
In src/server/routes/transaction/cancel.ts around lines 96-115, do not overwrite
nonce or gas or fabricate sentAt/sentAtBlock/userOpHash for queued cancels:
preserve the original transaction.nonce and transaction.gas, remove the sentAt
and sentAtBlock assignments and the dummy userOpHash/nonce="cancelled"/nonce:-1
values, and set isUserOp appropriately from transaction.isUserOp; also remove
the await getBlockNumberish(...) RPC call for cancels that were never sent. If
the CancelledTransaction type currently requires sentAt/sentAtBlock or a
userOpHash, update the type to make those fields optional or adjust downstream
consumers to handle "never sent" cases instead of injecting fake values.

} else if (transaction.status === "sent") {
if (!transaction.isUserOp) {
// Cancel a sent transaction with the same nonce.
const { chainId, from, nonce } = transaction;
const transactionHash = await sendCancellationTransaction({
Expand Down Expand Up @@ -143,7 +149,10 @@ export async function cancelTransaction(fastify: FastifyInstance) {
queueId,
status: "success",
message,
transactionHash: cancelledTransaction.sentTransactionHashes.at(-1),
transactionHash:
"sentTransactionHashes" in cancelledTransaction
? cancelledTransaction.sentTransactionHashes.at(-1)
: cancelledTransaction.userOpHash,
},
});
},
Expand Down
Loading