Skip to content

Commit c8d3c13

Browse files
committed
chore: track walletType usage
1 parent 4e9ea68 commit c8d3c13

File tree

9 files changed

+93
-86
lines changed

9 files changed

+93
-86
lines changed

src/db/wallets/getWalletDetails.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import LRUMap from "mnemonist/lru-map";
12
import { getAddress } from "thirdweb";
23
import { z } from "zod";
34
import type { PrismaTransaction } from "../../schema/prisma";
@@ -8,7 +9,7 @@ import { getPrismaWithPostgresTx } from "../client";
89

910
interface GetWalletDetailsParams {
1011
pgtx?: PrismaTransaction;
11-
address: string;
12+
walletAddress: string;
1213
}
1314

1415
export class WalletDetailsError extends Error {
@@ -130,6 +131,8 @@ export type SmartBackendWalletType = (typeof SmartBackendWalletTypes)[number];
130131
export type BackendWalletType = (typeof BackendWalletTypes)[number];
131132
export type ParsedWalletDetails = z.infer<typeof walletDetailsSchema>;
132133

134+
export const walletDetailsCache = new LRUMap<string, ParsedWalletDetails>(2048);
135+
133136
/**
134137
* Return the wallet details for the given address.
135138
*
@@ -143,20 +146,28 @@ export type ParsedWalletDetails = z.infer<typeof walletDetailsSchema>;
143146
*/
144147
export const getWalletDetails = async ({
145148
pgtx,
146-
address,
149+
walletAddress: _walletAddress,
147150
}: GetWalletDetailsParams) => {
151+
// Wallet details are stored in lowercase.
152+
const walletAddress = _walletAddress.toLowerCase();
153+
154+
const cachedDetails = walletDetailsCache.get(walletAddress);
155+
if (cachedDetails) {
156+
return cachedDetails;
157+
}
158+
148159
const prisma = getPrismaWithPostgresTx(pgtx);
149160
const config = await getConfig();
150161

151162
const walletDetails = await prisma.walletDetails.findUnique({
152163
where: {
153-
address: address.toLowerCase(),
164+
address: walletAddress,
154165
},
155166
});
156167

157168
if (!walletDetails) {
158169
throw new WalletDetailsError(
159-
`No wallet details found for address ${address}`,
170+
`No wallet details found for address ${walletAddress}`,
160171
);
161172
}
162173

@@ -167,7 +178,7 @@ export const getWalletDetails = async ({
167178
) {
168179
if (!walletDetails.awsKmsArn) {
169180
throw new WalletDetailsError(
170-
`AWS KMS ARN is missing for the wallet with address ${address}`,
181+
`AWS KMS ARN is missing for the wallet with address ${walletAddress}`,
171182
);
172183
}
173184

@@ -188,7 +199,7 @@ export const getWalletDetails = async ({
188199
) {
189200
if (!walletDetails.gcpKmsResourcePath) {
190201
throw new WalletDetailsError(
191-
`GCP KMS resource path is missing for the wallet with address ${address}`,
202+
`GCP KMS resource path is missing for the wallet with address ${walletAddress}`,
192203
);
193204
}
194205

@@ -209,14 +220,17 @@ export const getWalletDetails = async ({
209220

210221
// zod schema can validate all necessary fields are populated after decryption
211222
try {
212-
return walletDetailsSchema.parse(walletDetails, {
223+
const result = walletDetailsSchema.parse(walletDetails, {
213224
errorMap: (issue) => {
214225
const fieldName = issue.path.join(".");
215226
return {
216-
message: `${fieldName} is necessary for wallet ${address} of type ${walletDetails.type}, but not found in wallet details or configuration`,
227+
message: `${fieldName} is necessary for wallet ${walletAddress} of type ${walletDetails.type}, but not found in wallet details or configuration`,
217228
};
218229
},
219230
});
231+
232+
walletDetailsCache.set(walletAddress, result);
233+
return result;
220234
} catch (e) {
221235
if (e instanceof z.ZodError) {
222236
throw new WalletDetailsError(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export async function signMessageRoute(fastify: FastifyInstance) {
5656
}
5757

5858
const walletDetails = await getWalletDetails({
59-
address: walletAddress,
59+
walletAddress,
6060
});
6161

6262
if (isSmartBackendWallet(walletDetails) && !chainId) {

src/server/utils/wallets/getLocalWallet.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ export const getLocalWallet = async ({
4949
});
5050

5151
// If that works, save the wallet using the encryption password for the future
52-
const walletDetails = await getWalletDetails({ address: walletAddress });
52+
const walletDetails = await getWalletDetails({
53+
walletAddress,
54+
});
5355

5456
logger({
5557
service: "worker",
@@ -73,7 +75,9 @@ export const getLocalWallet = async ({
7375
export const getLocalWalletAccount = async (
7476
walletAddress: Address,
7577
): Promise<Account> => {
76-
const walletDetails = await getWalletDetails({ address: walletAddress });
78+
const walletDetails = await getWalletDetails({
79+
walletAddress,
80+
});
7781

7882
if (walletDetails.type !== "local") {
7983
throw new Error(`Local Wallet not found for address ${walletAddress}`);

src/utils/account.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const getAccount = async (args: {
3939
}
4040

4141
const walletDetails = await getWalletDetails({
42-
address: from,
42+
walletAddress: from,
4343
});
4444

4545
const { account } = await walletDetailsToAccount({ walletDetails, chain });
@@ -180,7 +180,7 @@ export const getSmartBackendWalletAdminAccount = async ({
180180
}
181181

182182
const walletDetails = await getWalletDetails({
183-
address: accountAddress,
183+
walletAddress: accountAddress,
184184
});
185185

186186
if (!isSmartBackendWallet(walletDetails)) {

src/utils/cache/clearCache.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { walletDetailsCache } from "../../db/wallets/getWalletDetails";
12
import type { env } from "../env";
23
import { accessTokenCache } from "./accessToken";
34
import { invalidateConfig } from "./getConfig";
45
import { sdkCache } from "./getSdk";
6+
import { smartWalletsCache } from "./getSmartWalletV5";
57
import { walletsCache } from "./getWallet";
68
import { webhookCache } from "./getWebhook";
79
import { keypairCache } from "./keypair";
@@ -15,4 +17,6 @@ export const clearCache = async (
1517
walletsCache.clear();
1618
accessTokenCache.clear();
1719
keypairCache.clear();
20+
smartWalletsCache.clear();
21+
walletDetailsCache.clear();
1822
};

src/utils/cache/getWallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const getWallet = async <TWallet extends EVMWallet>({
4444
try {
4545
walletDetails = await getWalletDetails({
4646
pgtx,
47-
address: walletAddress,
47+
walletAddress,
4848
});
4949
} catch (e) {
5050
if (e instanceof WalletDetailsError) {

src/utils/transaction/insertTransaction.ts

Lines changed: 51 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { StatusCodes } from "http-status-codes";
22
import { randomUUID } from "node:crypto";
33
import { TransactionDB } from "../../db/transactions/db";
44
import {
5+
ParsedWalletDetails,
56
getWalletDetails,
67
isSmartBackendWallet,
7-
type ParsedWalletDetails,
88
} from "../../db/wallets/getWalletDetails";
99
import { doesChainSupportService } from "../../lib/chain/chain-capabilities";
1010
import { createCustomError } from "../../server/middleware/error";
@@ -43,13 +43,40 @@ export const insertTransaction = async (
4343
}
4444
}
4545

46+
// Get wallet details. For EOA and SBW (v5 endpoints), `from` should return a valid backend wallet.
47+
// For SBW (v4 endpoints), `accountAddress` should return a valid backend wallet.
48+
// Else the provided details are incorrect (user error).
49+
let walletDetails: ParsedWalletDetails | undefined;
50+
let isSmartBackendWalletV4 = false;
51+
try {
52+
walletDetails = await getWalletDetails({
53+
walletAddress: insertedTransaction.from,
54+
});
55+
} catch {}
56+
if (!walletDetails && insertedTransaction.accountAddress) {
57+
try {
58+
walletDetails = await getWalletDetails({
59+
walletAddress: insertedTransaction.accountAddress,
60+
});
61+
isSmartBackendWalletV4 = true;
62+
} catch {}
63+
}
64+
if (!walletDetails) {
65+
throw createCustomError(
66+
"Account not found",
67+
StatusCodes.BAD_REQUEST,
68+
"ACCOUNT_NOT_FOUND",
69+
);
70+
}
71+
4672
let queuedTransaction: QueuedTransaction = {
4773
...insertedTransaction,
4874
status: "queued",
4975
queueId,
5076
queuedAt: new Date(),
5177
resendCount: 0,
5278

79+
walletType: walletDetails.type,
5380
from: getChecksumAddress(insertedTransaction.from),
5481
to: getChecksumAddress(insertedTransaction.to),
5582
signerAddress: getChecksumAddress(insertedTransaction.signerAddress),
@@ -60,37 +87,34 @@ export const insertTransaction = async (
6087
value: insertedTransaction.value ?? 0n,
6188
};
6289

63-
let walletDetails: ParsedWalletDetails | undefined;
90+
// Handle smart backend wallets details.
91+
if (isSmartBackendWallet(walletDetails)) {
92+
if (
93+
!(await doesChainSupportService(
94+
queuedTransaction.chainId,
95+
"account-abstraction",
96+
))
97+
) {
98+
throw createCustomError(
99+
`Smart backend wallets do not support chain ${queuedTransaction.chainId}.`,
100+
StatusCodes.BAD_REQUEST,
101+
"INVALID_SMART_BACKEND_WALLET_TRANSACTION",
102+
);
103+
}
64104

65-
try {
66-
walletDetails = await getWalletDetails({
67-
address: queuedTransaction.from,
68-
});
105+
queuedTransaction = {
106+
...queuedTransaction,
107+
accountFactoryAddress: walletDetails.accountFactoryAddress ?? undefined,
108+
entrypointAddress: walletDetails.entrypointAddress ?? undefined,
109+
};
69110

70-
// when using the v5 SDK with smart backend wallets, the following values are not set correctly:
71-
// isUserOp is set to false
72-
// account address is blank or the user provided value (this should be the SBW account address)
73-
// from is set to the SBW account address (this should be the SBW signer address)
74-
// these values need to be corrected so the worker can process the transaction
75-
if (isSmartBackendWallet(walletDetails)) {
111+
if (!isSmartBackendWalletV4) {
76112
if (queuedTransaction.accountAddress) {
113+
// Disallow smart backend wallets from sending userOps.
77114
throw createCustomError(
78-
"Smart backend wallets do not support interacting with other smart accounts",
115+
"Smart backend wallets do not support sending transactions with other smart accounts",
79116
StatusCodes.BAD_REQUEST,
80-
"INVALID_SMART_BACKEND_WALLET_INTERACTION",
81-
);
82-
}
83-
84-
if (
85-
!(await doesChainSupportService(
86-
queuedTransaction.chainId,
87-
"account-abstraction",
88-
))
89-
) {
90-
throw createCustomError(
91-
"Chain does not support smart backend wallets",
92-
StatusCodes.BAD_REQUEST,
93-
"SBW_CHAIN_NOT_SUPPORTED",
117+
"INVALID_SMART_BACKEND_WALLET_TRANSACTION",
94118
);
95119
}
96120

@@ -101,52 +125,8 @@ export const insertTransaction = async (
101125
from: walletDetails.accountSignerAddress,
102126
accountAddress: queuedTransaction.from,
103127
target: queuedTransaction.to,
104-
accountFactoryAddress: walletDetails.accountFactoryAddress ?? undefined,
105-
entrypointAddress: walletDetails.entrypointAddress ?? undefined,
106128
};
107129
}
108-
} catch {
109-
// if wallet details are not found, this is a smart backend wallet using a v4 endpoint
110-
}
111-
112-
if (!walletDetails && queuedTransaction.accountAddress) {
113-
try {
114-
walletDetails = await getWalletDetails({
115-
address: queuedTransaction.accountAddress,
116-
});
117-
118-
// when using v4 SDK with smart backend wallets, the following values are not set correctly:
119-
// entrypointAddress is not set
120-
// accountFactoryAddress is not set
121-
if (walletDetails && isSmartBackendWallet(walletDetails)) {
122-
if (
123-
!(await doesChainSupportService(
124-
queuedTransaction.chainId,
125-
"account-abstraction",
126-
))
127-
) {
128-
throw createCustomError(
129-
"Chain does not support smart backend wallets",
130-
StatusCodes.BAD_REQUEST,
131-
"SBW_CHAIN_NOT_SUPPORTED",
132-
);
133-
}
134-
135-
queuedTransaction = {
136-
...queuedTransaction,
137-
entrypointAddress: walletDetails.entrypointAddress ?? undefined,
138-
accountFactoryAddress:
139-
walletDetails.accountFactoryAddress ?? undefined,
140-
};
141-
}
142-
} catch {
143-
// if wallet details are not found for this either, this backend wallet does not exist at all
144-
throw createCustomError(
145-
"Account not found",
146-
StatusCodes.BAD_REQUEST,
147-
"ACCOUNT_NOT_FOUND",
148-
);
149-
}
150130
}
151131

152132
// Simulate the transaction.

src/utils/transaction/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Address, Hex, toSerializableTransaction } from "thirdweb";
22
import type { TransactionType } from "viem";
3+
import { BackendWalletType } from "../../db/wallets/getWalletDetails";
34

45
// TODO: Replace with thirdweb SDK exported type when available.
56
export type PopulatedTransaction = Awaited<
@@ -52,6 +53,7 @@ export type InsertedTransaction = {
5253
export type QueuedTransaction = InsertedTransaction & {
5354
status: "queued";
5455

56+
walletType: BackendWalletType;
5557
resendCount: number;
5658
queueId: string;
5759
queuedAt: Date;

src/utils/usage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Static } from "@sinclair/typebox";
22
import { UsageEvent } from "@thirdweb-dev/service-utils/cf-worker";
33
import { FastifyInstance } from "fastify";
44
import { Address, Hex } from "thirdweb";
5+
import { BackendWalletType } from "../db/wallets/getWalletDetails";
56
import { ADMIN_QUEUES_BASEPATH } from "../server/middleware/adminRoutes";
67
import { OPENAPI_ROUTES } from "../server/middleware/open-api";
78
import { contractParamSchema } from "../server/schemas/sharedApiSchemas";
@@ -22,6 +23,7 @@ export interface ReportUsageParams {
2223
| "api_request";
2324
input: {
2425
chainId?: number;
26+
walletType?: BackendWalletType;
2527
from?: Address;
2628
to?: Address;
2729
value?: bigint;
@@ -113,6 +115,7 @@ export const reportUsage = (usageEvents: ReportUsageParams[]) => {
113115
action,
114116
clientId: thirdwebClientId,
115117
chainId: input.chainId,
118+
walletType: input.walletType,
116119
walletAddress: input.from,
117120
contractAddress: input.to,
118121
transactionValue: input.value?.toString(),
@@ -136,7 +139,7 @@ export const reportUsage = (usageEvents: ReportUsageParams[]) => {
136139
logger({
137140
service: "worker",
138141
level: "error",
139-
message: `Error:`,
142+
message: "Error reporting usage event:",
140143
error: e,
141144
});
142145
}

0 commit comments

Comments
 (0)