Skip to content
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
107 changes: 107 additions & 0 deletions src/server/routes/backend-wallet/cancel-nonces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Type, type Static } from "@sinclair/typebox";
import type { FastifyInstance } from "fastify";
import { StatusCodes } from "http-status-codes";
import { eth_getTransactionCount, getRpcClient } from "thirdweb";
import { checksumAddress } from "thirdweb/utils";
import { getChain } from "../../../utils/chain";
import { thirdwebClient } from "../../../utils/sdk";
import { sendCancellationTransaction } from "../../../utils/transaction/cancelTransaction";
import {
requestQuerystringSchema,
standardResponseSchema,
} from "../../schemas/sharedApiSchemas";
import {
walletChainParamSchema,
walletHeaderSchema,
} from "../../schemas/wallet";
import { getChainIdFromChain } from "../../utils/chain";

const requestSchema = walletChainParamSchema;

const requestBodySchema = Type.Object({
toNonce: Type.Number({
description:
"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",
examples: ["42"],
}),
});

const responseBodySchema = Type.Object({
result: Type.Object(
{
cancelledNonces: Type.Array(Type.Number()),
},
{
examples: [
{
result: {
cancelledNonces: [11, 12, 13, 14, 15],
},
},
],
},
),
});

export async function cancelBackendWalletNoncesRoute(fastify: FastifyInstance) {
fastify.route<{
Params: Static<typeof requestSchema>;
Reply: Static<typeof responseBodySchema>;
Body: Static<typeof requestBodySchema>;
Querystring: Static<typeof requestQuerystringSchema>;
}>({
method: "POST",
url: "/backend-wallet/:chain/cancel-nonces",
schema: {
summary: "Cancel nonces",
description:
"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.",
tags: ["Backend Wallet"],
operationId: "cancelNonces",
params: requestSchema,
body: requestBodySchema,
headers: walletHeaderSchema,
querystring: requestQuerystringSchema,
response: {
...standardResponseSchema,
[StatusCodes.OK]: responseBodySchema,
},
},
handler: async (request, reply) => {
const { chain } = request.params;
const { toNonce } = request.body;
const { "x-backend-wallet-address": walletAddress } =
request.headers as Static<typeof walletHeaderSchema>;

const chainId = await getChainIdFromChain(chain);
const from = checksumAddress(walletAddress);

const rpcRequest = getRpcClient({
client: thirdwebClient,
chain: await getChain(chainId),
});

// Cancel starting from the next unused onchain nonce.
const transactionCount = await eth_getTransactionCount(rpcRequest, {
address: walletAddress,
blockTag: "latest",
});

const cancelledNonces: number[] = [];
for (let nonce = transactionCount; nonce <= toNonce; nonce++) {
await sendCancellationTransaction({
chainId,
from,
nonce,
});
cancelledNonces.push(nonce);
}

reply.status(StatusCodes.OK).send({
result: {
cancelledNonces,
},
});
},
});
}
3 changes: 3 additions & 0 deletions src/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { removePublicKey } from "./auth/keypair/remove";
import { getAllPermissions } from "./auth/permissions/getAll";
import { grantPermissions } from "./auth/permissions/grant";
import { revokePermissions } from "./auth/permissions/revoke";
import { cancelBackendWalletNoncesRoute } from "./backend-wallet/cancel-nonces";
import { createBackendWallet } from "./backend-wallet/create";
import { getAll } from "./backend-wallet/getAll";
import { getBalance } from "./backend-wallet/getBalance";
Expand Down Expand Up @@ -129,6 +130,8 @@ export async function withRoutes(fastify: FastifyInstance) {
await fastify.register(getTransactionsForBackendWallet);
await fastify.register(getTransactionsForBackendWalletByNonce);
await fastify.register(resetBackendWalletNoncesRoute);
await fastify.register(cancelBackendWalletNoncesRoute);
await fastify.register(resetBackendWalletNoncesRoute);
await fastify.register(getBackendWalletNonce);
await fastify.register(simulateTransaction);

Expand Down
2 changes: 1 addition & 1 deletion src/server/routes/transaction/getAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const requestQuerySchema = Type.Object({
),
});

export const responseBodySchema = Type.Object({
const responseBodySchema = Type.Object({
result: Type.Object({
transactions: Type.Array(TransactionSchema),
totalCount: Type.Integer(),
Expand Down
2 changes: 1 addition & 1 deletion src/worker/tasks/nonceResyncWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const initNonceResyncWorker = async () => {
* This is to unblock a wallet that has been stuck due to one or more skipped nonces.
*/
const handler: Processor<any, void, string> = async (job: Job<string>) => {
const sentNoncesKeys = await redis.keys("nonce-sent*");
const sentNoncesKeys = await redis.keys("nonce-sent:*");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

typo but doesn't actually change behavior much since the previous command should have worked too.

if (sentNoncesKeys.length === 0) {
job.log("No active wallets.");
return;
Expand Down
Loading