Skip to content

Commit b4aeadc

Browse files
committed
Merge remote-tracking branch 'origin/main' into ph/cancelNonce
2 parents 652206c + a364d40 commit b4aeadc

File tree

14 files changed

+131
-99
lines changed

14 files changed

+131
-99
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
"schema": "./src/prisma/schema.prisma"
102102
},
103103
"resolutions": {
104-
"@thirdweb-dev/auth/**/axios": ">=1.7.4",
104+
"@thirdweb-dev/auth/**/axios": ">=1.7.8",
105105
"@thirdweb-dev/auth/**/web3-utils": ">=4.2.1",
106106
"ethers-gcp-kms-signer/**/protobufjs": ">=7.2.5",
107107
"fastify/**/find-my-way": ">=8.2.2",

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).

src/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const initServer = async () => {
6565

6666
// Start the server with middleware.
6767
const server: FastifyInstance = fastify({
68+
maxParamLength: 200,
6869
connectionTimeout: SERVER_CONNECTION_TIMEOUT,
6970
disableRequestLogging: true,
7071
trustProxy,
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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { getTransactionsForBackendWallet } from "./backend-wallet/getTransaction
2020
import { getTransactionsForBackendWalletByNonce } from "./backend-wallet/getTransactionsByNonce";
2121
import { importBackendWallet } from "./backend-wallet/import";
2222
import { removeBackendWallet } from "./backend-wallet/remove";
23-
import { resetBackendWalletNonces } from "./backend-wallet/resetNonces";
23+
import { resetBackendWalletNoncesRoute } from "./backend-wallet/reset-nonces";
2424
import { sendTransaction } from "./backend-wallet/sendTransaction";
2525
import { sendTransactionBatch } from "./backend-wallet/sendTransactionBatch";
2626
import { signMessageRoute } from "./backend-wallet/signMessage";
@@ -129,8 +129,9 @@ export async function withRoutes(fastify: FastifyInstance) {
129129
await fastify.register(signTypedData);
130130
await fastify.register(getTransactionsForBackendWallet);
131131
await fastify.register(getTransactionsForBackendWalletByNonce);
132-
await fastify.register(resetBackendWalletNonces);
132+
await fastify.register(resetBackendWalletNoncesRoute);
133133
await fastify.register(cancelBackendWalletNoncesRoute);
134+
await fastify.register(resetBackendWalletNoncesRoute);
134135
await fastify.register(getBackendWalletNonce);
135136
await fastify.register(simulateTransaction);
136137

src/server/routes/system/health.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { StatusCodes } from "http-status-codes";
44
import { isDatabaseReachable } from "../../../db/client";
55
import { env } from "../../../utils/env";
66
import { isRedisReachable } from "../../../utils/redis/redis";
7+
import { thirdwebClientId } from "../../../utils/sdk";
78
import { createCustomError } from "../../middleware/error";
89

910
type EngineFeature =
@@ -26,6 +27,7 @@ const ReplySchemaOk = Type.Object({
2627
Type.Literal("SMART_BACKEND_WALLETS"),
2728
]),
2829
),
30+
clientId: Type.String(),
2931
});
3032

3133
const ReplySchemaError = Type.Object({
@@ -73,6 +75,7 @@ export async function healthCheck(fastify: FastifyInstance) {
7375
engineVersion: env.ENGINE_VERSION,
7476
engineTier: env.ENGINE_TIER ?? "SELF_HOSTED",
7577
features: getFeatures(),
78+
clientId: thirdwebClientId,
7679
});
7780
},
7881
});

src/server/schemas/transaction/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export const toTransactionSchema = (
266266
if (transaction.status === "sent") {
267267
return transaction.gasPrice?.toString() ?? null;
268268
}
269-
return null;
269+
return transaction.overrides?.gasPrice?.toString() ?? null;
270270
};
271271

272272
const resolveMaxFeePerGas = (): string | null => {

src/server/schemas/txOverrides.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ export const txOverridesSchema = Type.Object({
1010
description: "Gas limit for the transaction",
1111
}),
1212

13-
// Overriding `gasPrice` is currently not supported.
14-
13+
gasPrice: Type.Optional({
14+
...WeiAmountStringSchema,
15+
description:
16+
"Gas price for the transaction. Do not use this if maxFeePerGas is set or if you want to use EIP-1559 type transactions. Only use this if you want to use legacy transactions.",
17+
}),
1518
maxFeePerGas: Type.Optional({
1619
...WeiAmountStringSchema,
1720
description: "Maximum fee per gas",

src/server/schemas/wallet/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const walletHeaderSchema = Type.Object({
1212
},
1313
"x-idempotency-key": Type.Optional(
1414
Type.String({
15+
maxLength: 200,
1516
description: `Transactions submitted with the same idempotency key will be de-duplicated. Only the last ${env.TRANSACTION_HISTORY_COUNT} transactions are compared.`,
1617
}),
1718
),

0 commit comments

Comments
 (0)