Skip to content

Commit a52a717

Browse files
authored
feat: cancel nonces endpoint (#788)
* feat: cancel nonces endpoint * remove exception
1 parent a364d40 commit a52a717

File tree

4 files changed

+112
-2
lines changed

4 files changed

+112
-2
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Type, type Static } from "@sinclair/typebox";
2+
import type { FastifyInstance } from "fastify";
3+
import { StatusCodes } from "http-status-codes";
4+
import { eth_getTransactionCount, getRpcClient } from "thirdweb";
5+
import { checksumAddress } from "thirdweb/utils";
6+
import { getChain } from "../../../utils/chain";
7+
import { thirdwebClient } from "../../../utils/sdk";
8+
import { sendCancellationTransaction } from "../../../utils/transaction/cancelTransaction";
9+
import {
10+
requestQuerystringSchema,
11+
standardResponseSchema,
12+
} from "../../schemas/sharedApiSchemas";
13+
import {
14+
walletChainParamSchema,
15+
walletHeaderSchema,
16+
} from "../../schemas/wallet";
17+
import { getChainIdFromChain } from "../../utils/chain";
18+
19+
const requestSchema = walletChainParamSchema;
20+
21+
const requestBodySchema = Type.Object({
22+
toNonce: Type.Number({
23+
description:
24+
"The nonce to cancel up to, inclusive. Example: If the onchain nonce is 10 and 'toNonce' is 15, this request will cancel nonces: 11, 12, 13, 14, 15",
25+
examples: ["42"],
26+
}),
27+
});
28+
29+
const responseBodySchema = Type.Object({
30+
result: Type.Object(
31+
{
32+
cancelledNonces: Type.Array(Type.Number()),
33+
},
34+
{
35+
examples: [
36+
{
37+
result: {
38+
cancelledNonces: [11, 12, 13, 14, 15],
39+
},
40+
},
41+
],
42+
},
43+
),
44+
});
45+
46+
export async function cancelBackendWalletNoncesRoute(fastify: FastifyInstance) {
47+
fastify.route<{
48+
Params: Static<typeof requestSchema>;
49+
Reply: Static<typeof responseBodySchema>;
50+
Body: Static<typeof requestBodySchema>;
51+
Querystring: Static<typeof requestQuerystringSchema>;
52+
}>({
53+
method: "POST",
54+
url: "/backend-wallet/:chain/cancel-nonces",
55+
schema: {
56+
summary: "Cancel nonces",
57+
description:
58+
"Cancel all nonces up to the provided nonce. This is useful to unblock a backend wallet that has transactions waiting for nonces to be mined.",
59+
tags: ["Backend Wallet"],
60+
operationId: "cancelNonces",
61+
params: requestSchema,
62+
body: requestBodySchema,
63+
headers: walletHeaderSchema,
64+
querystring: requestQuerystringSchema,
65+
response: {
66+
...standardResponseSchema,
67+
[StatusCodes.OK]: responseBodySchema,
68+
},
69+
},
70+
handler: async (request, reply) => {
71+
const { chain } = request.params;
72+
const { toNonce } = request.body;
73+
const { "x-backend-wallet-address": walletAddress } =
74+
request.headers as Static<typeof walletHeaderSchema>;
75+
76+
const chainId = await getChainIdFromChain(chain);
77+
const from = checksumAddress(walletAddress);
78+
79+
const rpcRequest = getRpcClient({
80+
client: thirdwebClient,
81+
chain: await getChain(chainId),
82+
});
83+
84+
// Cancel starting from the next unused onchain nonce.
85+
const transactionCount = await eth_getTransactionCount(rpcRequest, {
86+
address: walletAddress,
87+
blockTag: "latest",
88+
});
89+
90+
const cancelledNonces: number[] = [];
91+
for (let nonce = transactionCount; nonce <= toNonce; nonce++) {
92+
await sendCancellationTransaction({
93+
chainId,
94+
from,
95+
nonce,
96+
});
97+
cancelledNonces.push(nonce);
98+
}
99+
100+
reply.status(StatusCodes.OK).send({
101+
result: {
102+
cancelledNonces,
103+
},
104+
});
105+
},
106+
});
107+
}

src/server/routes/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { removePublicKey } from "./auth/keypair/remove";
1111
import { getAllPermissions } from "./auth/permissions/getAll";
1212
import { grantPermissions } from "./auth/permissions/grant";
1313
import { revokePermissions } from "./auth/permissions/revoke";
14+
import { cancelBackendWalletNoncesRoute } from "./backend-wallet/cancel-nonces";
1415
import { createBackendWallet } from "./backend-wallet/create";
1516
import { getAll } from "./backend-wallet/getAll";
1617
import { getBalance } from "./backend-wallet/getBalance";
@@ -129,6 +130,8 @@ export async function withRoutes(fastify: FastifyInstance) {
129130
await fastify.register(getTransactionsForBackendWallet);
130131
await fastify.register(getTransactionsForBackendWalletByNonce);
131132
await fastify.register(resetBackendWalletNoncesRoute);
133+
await fastify.register(cancelBackendWalletNoncesRoute);
134+
await fastify.register(resetBackendWalletNoncesRoute);
132135
await fastify.register(getBackendWalletNonce);
133136
await fastify.register(simulateTransaction);
134137

src/server/routes/transaction/getAll.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const requestQuerySchema = Type.Object({
2727
),
2828
});
2929

30-
export const responseBodySchema = Type.Object({
30+
const responseBodySchema = Type.Object({
3131
result: Type.Object({
3232
transactions: Type.Array(TransactionSchema),
3333
totalCount: Type.Integer(),

src/worker/tasks/nonceResyncWorker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const initNonceResyncWorker = async () => {
4040
* This is to unblock a wallet that has been stuck due to one or more skipped nonces.
4141
*/
4242
const handler: Processor<any, void, string> = async (job: Job<string>) => {
43-
const sentNoncesKeys = await redis.keys("nonce-sent*");
43+
const sentNoncesKeys = await redis.keys("nonce-sent:*");
4444
if (sentNoncesKeys.length === 0) {
4545
job.log("No active wallets.");
4646
return;

0 commit comments

Comments
 (0)