Skip to content

Commit 789340a

Browse files
authored
feat: allow resetting the nonce for a single wallet (#786)
* feat: allow resetting the nonce for a single wallet * fix build * add body
1 parent 56a9b3b commit 789340a

File tree

4 files changed

+110
-91
lines changed

4 files changed

+110
-91
lines changed

src/db/wallets/walletNonce.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -250,18 +250,19 @@ export const inspectNonce = async (chainId: number, walletAddress: Address) => {
250250
};
251251

252252
/**
253-
* Delete all wallet nonces. Useful when they get out of sync.
253+
* Delete nonce state for the provided wallets.
254+
* @param backendWallets
254255
*/
255-
export const deleteAllNonces = async () => {
256-
const keys = [
257-
...(await redis.keys("nonce:*")),
258-
...(await redis.keys("nonce-recycled:*")),
259-
...(await redis.keys("sent-nonce:*")),
260-
];
261-
if (keys.length > 0) {
262-
await redis.del(keys);
263-
}
264-
};
256+
export async function deleteNoncesForBackendWallets(
257+
backendWallets: { chainId: number; walletAddress: Address }[],
258+
) {
259+
const keys = backendWallets.flatMap(({ chainId, walletAddress }) => [
260+
lastUsedNonceKey(chainId, walletAddress),
261+
recycledNoncesKey(chainId, walletAddress),
262+
sentNoncesKey(chainId, walletAddress),
263+
]);
264+
await redis.del(keys);
265+
}
265266

266267
/**
267268
* Resync the nonce to the higher of (db nonce, onchain nonce).
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Type, type Static } from "@sinclair/typebox";
2+
import type { FastifyInstance } from "fastify";
3+
import { StatusCodes } from "http-status-codes";
4+
import { getAddress } from "thirdweb";
5+
import {
6+
deleteNoncesForBackendWallets,
7+
getUsedBackendWallets,
8+
syncLatestNonceFromOnchain,
9+
} from "../../../db/wallets/walletNonce";
10+
import { AddressSchema } from "../../schemas/address";
11+
import { standardResponseSchema } from "../../schemas/sharedApiSchemas";
12+
13+
const requestBodySchema = Type.Object({
14+
chainId: Type.Optional(
15+
Type.Number({
16+
description: "The chain ID to reset nonces for.",
17+
}),
18+
),
19+
walletAddress: Type.Optional({
20+
...AddressSchema,
21+
description:
22+
"The backend wallet address to reset nonces for. Omit to reset all backend wallets.",
23+
}),
24+
});
25+
26+
const responseSchema = Type.Object({
27+
result: Type.Object({
28+
status: Type.String(),
29+
count: Type.Number({
30+
description: "The number of backend wallets processed.",
31+
}),
32+
}),
33+
});
34+
35+
responseSchema.example = {
36+
result: {
37+
status: "success",
38+
count: 1,
39+
},
40+
};
41+
42+
export const resetBackendWalletNoncesRoute = async (
43+
fastify: FastifyInstance,
44+
) => {
45+
fastify.route<{
46+
Reply: Static<typeof responseSchema>;
47+
Body: Static<typeof requestBodySchema>;
48+
}>({
49+
method: "POST",
50+
url: "/backend-wallet/reset-nonces",
51+
schema: {
52+
summary: "Reset nonces",
53+
description:
54+
"Reset nonces for all backend wallets. This is for debugging purposes and does not impact held tokens.",
55+
tags: ["Backend Wallet"],
56+
operationId: "resetNonces",
57+
body: requestBodySchema,
58+
response: {
59+
...standardResponseSchema,
60+
[StatusCodes.OK]: responseSchema,
61+
},
62+
},
63+
handler: async (req, reply) => {
64+
const { chainId, walletAddress: _walletAddress } = req.body;
65+
66+
// If chain+wallet are provided, only process that wallet.
67+
// Otherwise process all used wallets that has nonce state.
68+
const backendWallets =
69+
chainId && _walletAddress
70+
? [{ chainId, walletAddress: getAddress(_walletAddress) }]
71+
: await getUsedBackendWallets();
72+
73+
const RESYNC_BATCH_SIZE = 50;
74+
for (let i = 0; i < backendWallets.length; i += RESYNC_BATCH_SIZE) {
75+
const batch = backendWallets.slice(i, i + RESYNC_BATCH_SIZE);
76+
77+
// Delete nonce state for these backend wallets.
78+
await deleteNoncesForBackendWallets(backendWallets);
79+
80+
// Resync nonces for these backend wallets.
81+
await Promise.allSettled(
82+
batch.map(({ chainId, walletAddress }) =>
83+
syncLatestNonceFromOnchain(chainId, walletAddress),
84+
),
85+
);
86+
}
87+
88+
reply.status(StatusCodes.OK).send({
89+
result: {
90+
status: "success",
91+
count: backendWallets.length,
92+
},
93+
});
94+
},
95+
});
96+
};

src/server/routes/backend-wallet/resetNonces.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/server/routes/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { getTransactionsForBackendWallet } from "./backend-wallet/getTransaction
1919
import { getTransactionsForBackendWalletByNonce } from "./backend-wallet/getTransactionsByNonce";
2020
import { importBackendWallet } from "./backend-wallet/import";
2121
import { removeBackendWallet } from "./backend-wallet/remove";
22-
import { resetBackendWalletNonces } from "./backend-wallet/resetNonces";
22+
import { resetBackendWalletNoncesRoute } from "./backend-wallet/reset-nonces";
2323
import { sendTransaction } from "./backend-wallet/sendTransaction";
2424
import { sendTransactionBatch } from "./backend-wallet/sendTransactionBatch";
2525
import { signMessageRoute } from "./backend-wallet/signMessage";
@@ -128,7 +128,7 @@ export async function withRoutes(fastify: FastifyInstance) {
128128
await fastify.register(signTypedData);
129129
await fastify.register(getTransactionsForBackendWallet);
130130
await fastify.register(getTransactionsForBackendWalletByNonce);
131-
await fastify.register(resetBackendWalletNonces);
131+
await fastify.register(resetBackendWalletNoncesRoute);
132132
await fastify.register(getBackendWalletNonce);
133133
await fastify.register(simulateTransaction);
134134

0 commit comments

Comments
 (0)