From 00e126b48e7323a88a84f647302896d07f3a98a0 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Wed, 11 Dec 2024 23:37:07 +0800 Subject: [PATCH] wip: auth for lite mode --- src/server/middleware/auth.ts | 219 ++++++++++++------ src/server/middleware/engine-mode.ts | 55 ++++- src/server/middleware/logs.ts | 2 +- .../routes/auth/access-tokens/create.ts | 8 +- .../routes/backend-wallet/lite/create.ts | 30 +-- src/server/routes/backend-wallet/lite/get.ts | 22 +- .../routes/backend-wallet/sign-message.ts | 2 + src/server/utils/auth.ts | 27 +++ src/shared/utils/account.ts | 4 +- src/shared/utils/env.ts | 2 +- 10 files changed, 259 insertions(+), 112 deletions(-) create mode 100644 src/server/utils/auth.ts diff --git a/src/server/middleware/auth.ts b/src/server/middleware/auth.ts index 79690e37..830cd04c 100644 --- a/src/server/middleware/auth.ts +++ b/src/server/middleware/auth.ts @@ -26,34 +26,66 @@ import { sendWebhookRequest } from "../../shared/utils/webhook"; import { Permission } from "../../shared/schemas/auth"; import { ADMIN_QUEUES_BASEPATH } from "./admin-routes"; import { OPENAPI_ROUTES } from "./open-api"; - -export type TAuthData = never; -export type TAuthSession = { permissions: string }; - -interface AuthResponse { +import { StatusCodes } from "http-status-codes"; + +type TAuthData = never; +type TAuthSession = { permissions: string }; + +export type AuthenticationType = + | "public" + | "dashboard" + | "access-token" + | "secret-key" + /** @deprecated */ + | "webhook" + /** @deprecated */ + | "websocket" + | "lite"; +const ALLOWED_AUTHENTICATION_TYPES = + env.ENGINE_MODE === "lite" + ? new Set(["dashboard", "lite"]) + : new Set([ + "dashboard", + "access-token", + "webhook", + "websocket", + ]); + +type AuthResponse = { isAuthed: boolean; - user?: ThirdwebAuthUser; // If error is provided, return an error immediately. error?: string; -} +}; +// Store metadata about the authenticated request on the request object. declare module "fastify" { interface FastifyRequest { - user: ThirdwebAuthUser; + authentication: + | { + type: "dashboard" | "access-token" | "secret-key"; + user: ThirdwebAuthUser; + } + | { + type: "lite"; + thirdwebSecretKey: string; + litePassword: string; + }; } } export async function withAuth(server: FastifyInstance) { + // All endpoints in sandbox mode are unauthed. + if (env.ENGINE_MODE === "sandbox") { + return; + } + const config = await getConfig(); - // Configure the ThirdwebAuth fastify plugin const { authRouter, authMiddleware, getUser } = ThirdwebAuth< TAuthData, TAuthSession >({ - // TODO: Domain needs to be pulled from config as well domain: config.authDomain, - // We use an async wallet here to load wallet from config every time wallet: new AsyncWallet({ getSigner: async () => { const authWallet = await getAuthWallet(); @@ -108,47 +140,41 @@ export async function withAuth(server: FastifyInstance) { // Note: in the onRequest hook, request.body will always be undefined, because the body parsing happens before the preValidation hook. // https://fastify.dev/docs/latest/Reference/Hooks/#onrequest server.addHook("preValidation", async (req, res) => { - // Skip auth check in sandbox mode - if (env.ENGINE_MODE === "sandbox") { - return; - } - let message = - "Please provide a valid access token or other authentication. See: https://portal.thirdweb.com/engine/features/access-tokens"; - try { - const { isAuthed, user, error } = await onRequest({ req, getUser }); - if (isAuthed) { - if (user) { - req.user = user; - } - // Allow this request to proceed. + const authResponse = await onRequest({ req, getUser }); + if (authResponse.isAuthed) { return; - } else if (error) { - message = error; } - } catch (err: any) { + if (authResponse.error) { + return res.status(StatusCodes.UNAUTHORIZED).send({ + error: "UNAUTHORIZED", + message: authResponse.error, + }); + } + } catch (error) { logger({ service: "server", level: "warn", message: "Error authenticating user", - error: err, + error, }); } - return res.status(401).send({ - error: "Unauthorized", - message, + return res.status(StatusCodes.UNAUTHORIZED).send({ + error: "UNAUTHORIZED", + message: + "Please provide a valid access token. See: https://portal.thirdweb.com/engine/features/access-tokens", }); }); } -export const onRequest = async ({ +async function onRequest({ req, getUser, }: { req: FastifyRequest; getUser: ReturnType>["getUser"]; -}): Promise => { +}): Promise { // Handle websocket auth separately. if (req.headers.upgrade?.toLowerCase() === "websocket") { return handleWebsocketAuth(req, getUser); @@ -162,6 +188,7 @@ export const onRequest = async ({ const jwt = getJWT(req); if (jwt) { const decoded = jsonwebtoken.decode(jwt, { complete: true }); + if (decoded) { const payload = decoded.payload as JwtPayload; const header = decoded.header; @@ -172,11 +199,11 @@ export const onRequest = async ({ const authWallet = await getAuthWallet(); if (publicKey === (await authWallet.getAddress())) { return await handleAccessToken(jwt, req, getUser); - } else if (publicKey === THIRDWEB_DASHBOARD_ISSUER) { - return await handleDashboardAuth(jwt); - } else { - return await handleKeypairAuth({ jwt, req, publicKey }); } + if (publicKey === THIRDWEB_DASHBOARD_ISSUER) { + return await handleDashboardAuth({ jwt, req }); + } + return await handleKeypairAuth({ jwt, req, publicKey }); } // Get the public key hash from the `kid` header. @@ -187,6 +214,11 @@ export const onRequest = async ({ } } + const liteModeResp = await handleLiteModeAuth(req); + if (liteModeResp.isAuthed) { + return liteModeResp; + } + const secretKeyResp = await handleSecretKey(req); if (secretKeyResp.isAuthed) { return secretKeyResp; @@ -199,7 +231,7 @@ export const onRequest = async ({ // Unauthorized: no auth patterns matched. return { isAuthed: false }; -}; +} /** * Handles unauthed routes. @@ -254,10 +286,14 @@ const handlePublicEndpoints = (req: FastifyRequest): AuthResponse => { * @returns AuthResponse * @async */ -const handleWebsocketAuth = async ( +async function handleWebsocketAuth( req: FastifyRequest, getUser: ReturnType>["getUser"], -): Promise => { +): Promise { + if (!ALLOWED_AUTHENTICATION_TYPES.has("websocket")) { + return { isAuthed: false }; + } + const { token: jwt } = req.query as { token: string }; const token = await getAccessToken({ jwt }); @@ -284,7 +320,7 @@ const handleWebsocketAuth = async ( user?.session?.permissions === Permission.Owner || user?.session?.permissions === Permission.Admin ) { - return { isAuthed: true, user }; + return { isAuthed: true }; } } @@ -292,7 +328,7 @@ const handleWebsocketAuth = async ( req.raw.socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n"); req.raw.socket.destroy(); return { isAuthed: false }; -}; +} /** * Auth via keypair. @@ -303,12 +339,12 @@ const handleWebsocketAuth = async ( * @param publicKey string * @returns AuthResponse */ -const handleKeypairAuth = async (args: { +async function handleKeypairAuth(args: { jwt: string; req: FastifyRequest; publicKey?: string; publicKeyHash?: string; -}): Promise => { +}): Promise { // The keypair auth feature must be explicitly enabled. if (!env.ENABLE_KEYPAIR_AUTH) { return { isAuthed: false }; @@ -363,7 +399,7 @@ const handleKeypairAuth = async (args: { } return { isAuthed: false, error }; -}; +} /** * Auth via access token. @@ -374,16 +410,19 @@ const handleKeypairAuth = async (args: { * @returns AuthResponse * @async */ -const handleAccessToken = async ( +async function handleAccessToken( jwt: string, req: FastifyRequest, getUser: ReturnType>["getUser"], -): Promise => { - let token: Awaited> = null; +): Promise { + if (!ALLOWED_AUTHENTICATION_TYPES.has("access-token")) { + return { isAuthed: false }; + } + let token: Awaited> = null; try { token = await getAccessToken({ jwt }); - } catch (e) { + } catch { // Missing or invalid signature. This will occur if the JWT not intended for this auth pattern. return { isAuthed: false }; } @@ -415,8 +454,12 @@ const handleAccessToken = async ( }; } - return { isAuthed: true, user }; -}; + req.authentication = { + type: "access-token", + user, + }; + return { isAuthed: true }; +} /** * Auth via dashboard. @@ -425,7 +468,14 @@ const handleAccessToken = async ( * @returns AuthResponse * @async */ -const handleDashboardAuth = async (jwt: string): Promise => { +async function handleDashboardAuth({ + req, + jwt, +}: { req: FastifyRequest; jwt: string }): Promise { + if (!ALLOWED_AUTHENTICATION_TYPES.has("dashboard")) { + return { isAuthed: false }; + } + const user = (await handleSiwe(jwt, "thirdweb.com", THIRDWEB_DASHBOARD_ISSUER)) || (await handleSiwe(jwt, "thirdweb-preview.com", THIRDWEB_DASHBOARD_ISSUER)); @@ -435,8 +485,8 @@ const handleDashboardAuth = async (jwt: string): Promise => { res?.permissions === Permission.Owner || res?.permissions === Permission.Admin ) { - return { - isAuthed: true, + req.authentication = { + type: "dashboard", user: { address: user.address, session: { @@ -444,11 +494,12 @@ const handleDashboardAuth = async (jwt: string): Promise => { }, }, }; + return { isAuthed: true }; } } return { isAuthed: false }; -}; +} /** * Auth via thirdweb secret key. @@ -457,12 +508,12 @@ const handleDashboardAuth = async (jwt: string): Promise => { * @param req FastifyRequest * @returns */ -const handleSecretKey = async (req: FastifyRequest): Promise => { +async function handleSecretKey(req: FastifyRequest): Promise { const thirdwebApiSecretKey = req.headers.authorization?.split(" ")[1]; if (thirdwebApiSecretKey === env.THIRDWEB_API_SECRET_KEY) { const authWallet = await getAuthWallet(); - return { - isAuthed: true, + req.authentication = { + type: "secret-key", user: { address: await authWallet.getAddress(), session: { @@ -470,10 +521,11 @@ const handleSecretKey = async (req: FastifyRequest): Promise => { }, }, }; + return { isAuthed: true }; } return { isAuthed: false }; -}; +} /** * Auth via auth webhooks @@ -483,9 +535,11 @@ const handleSecretKey = async (req: FastifyRequest): Promise => { * @returns AuthResponse * @async */ -const handleAuthWebhooks = async ( - req: FastifyRequest, -): Promise => { +async function handleAuthWebhooks(req: FastifyRequest): Promise { + if (!ALLOWED_AUTHENTICATION_TYPES.has("webhook")) { + return { isAuthed: false }; + } + const authWebhooks = await getWebhooksByEventType(WebhooksEventTypes.AUTH); if (authWebhooks.length > 0) { const authResponses = await Promise.all( @@ -509,13 +563,42 @@ const handleAuthWebhooks = async ( } return { isAuthed: false }; -}; +} + +async function handleLiteModeAuth(req: FastifyRequest): Promise { + if (!ALLOWED_AUTHENTICATION_TYPES.has("lite")) { + return { isAuthed: false }; + } -const hashRequestBody = (req: FastifyRequest): string => { + const litePassword = req.headers.authorization?.split(" ")[1]; + if (!litePassword) { + return { + isAuthed: false, + error: 'Missing "Authorization" header.', + }; + } + + const thirdwebSecretKey = req.headers["x-thirdweb-secret-key"]; + if (!thirdwebSecretKey) { + return { + isAuthed: false, + error: 'Missing "x-thirdweb-secret-key" header.', + }; + } + + req.authentication = { + type: "lite", + litePassword, + thirdwebSecretKey: String(thirdwebSecretKey), + }; + return { isAuthed: true }; +} + +function hashRequestBody(req: FastifyRequest): string { return createHash("sha256") .update(JSON.stringify(req.body), "utf8") .digest("hex"); -}; +} /** * Check if the request IP is in the allowlist. @@ -524,9 +607,7 @@ const hashRequestBody = (req: FastifyRequest): string => { * @returns boolean * @async */ -const checkIpInAllowlist = async ( - req: FastifyRequest, -): Promise<{ isAllowed: boolean; ip: string }> => { +async function checkIpInAllowlist(req: FastifyRequest) { let ip = req.ip; const trustProxy = env.TRUST_PROXY || !!env.ENGINE_TIER; if (trustProxy && req.headers["cf-connecting-ip"]) { @@ -542,4 +623,4 @@ const checkIpInAllowlist = async ( isAllowed: config.ipAllowlist.includes(ip), ip, }; -}; +} diff --git a/src/server/middleware/engine-mode.ts b/src/server/middleware/engine-mode.ts index 9abd2c22..c9db398c 100644 --- a/src/server/middleware/engine-mode.ts +++ b/src/server/middleware/engine-mode.ts @@ -1,17 +1,52 @@ -import type { FastifyInstance } from "fastify"; +import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import { env } from "../../shared/utils/env"; +import { StatusCodes } from "http-status-codes"; export function withEnforceEngineMode(server: FastifyInstance) { - if (env.ENGINE_MODE === "sandbox") { - server.addHook("onRequest", async (request, reply) => { - if (request.method !== "GET") { - return reply.status(405).send({ - statusCode: 405, - error: "Engine is in read-only mode. Only GET requests are allowed.", - message: - "Engine is in read-only mode. Only GET requests are allowed.", - }); + switch (env.ENGINE_MODE) { + case "lite": + server.addHook("onRequest", enforceLiteMode); + break; + case "sandbox": + server.addHook("onRequest", enforceSandboxMode); + break; + } +} + +const ALLOWED_LITE_MODE_PATHS_GET = new Set(["/backend-wallet/lite/:teamId"]); +const ALLOWED_LITE_MODE_PATHS_POST = new Set([ + "/backend-wallet/lite/:teamId", + "/backend-wallet/sign-message", +]); +async function enforceLiteMode(request: FastifyRequest, reply: FastifyReply) { + if (request.routeOptions.url) { + if (request.method === "GET") { + if (ALLOWED_LITE_MODE_PATHS_GET.has(request.routeOptions.url)) { + return; + } + } else if (request.method === "POST") { + if (ALLOWED_LITE_MODE_PATHS_POST.has(request.routeOptions.url)) { + return; } + } + } + + return reply.status(StatusCodes.FORBIDDEN).send({ + statusCode: StatusCodes.FORBIDDEN, + message: "Engine is in lite mode. Only limited endpoints are allowed.", + error: "ENGINE_MODE_FORBIDDEN", + }); +} + +async function enforceSandboxMode( + request: FastifyRequest, + reply: FastifyReply, +) { + if (request.method !== "GET") { + return reply.status(StatusCodes.FORBIDDEN).send({ + statusCode: StatusCodes.FORBIDDEN, + message: "Engine is in sandbox mode. Only GET requests are allowed.", + error: "ENGINE_MODE_FORBIDDEN", }); } } diff --git a/src/server/middleware/logs.ts b/src/server/middleware/logs.ts index 59ec9329..d78db85b 100644 --- a/src/server/middleware/logs.ts +++ b/src/server/middleware/logs.ts @@ -15,7 +15,7 @@ const IGNORE_LOG_PATHS = new Set([ const SENSITIVE_LOG_PATHS = new Set([ "/backend-wallet/import", "/configuration/wallets", - "/backend-wallet/lite", + "/backend-wallet/lite/:teamId", ]); function shouldLog(request: FastifyRequest) { diff --git a/src/server/routes/auth/access-tokens/create.ts b/src/server/routes/auth/access-tokens/create.ts index 53502150..64fec1f3 100644 --- a/src/server/routes/auth/access-tokens/create.ts +++ b/src/server/routes/auth/access-tokens/create.ts @@ -10,6 +10,7 @@ import { getConfig } from "../../../../shared/utils/cache/get-config"; import { env } from "../../../../shared/utils/env"; import { standardResponseSchema } from "../../../schemas/shared-api-schemas"; import { AccessTokenSchema } from "./get-all"; +import { assertAuthenticationType } from "../../../utils/auth"; const requestBodySchema = Type.Object({ label: Type.Optional(Type.String()), @@ -41,8 +42,9 @@ export async function createAccessToken(fastify: FastifyInstance) { }, }, handler: async (req, res) => { - const { label } = req.body; + assertAuthenticationType(req, ["dashboard", "secret-key"]); + const { label } = req.body; const config = await getConfig(); const wallet = new LocalWallet(); @@ -75,13 +77,13 @@ export async function createAccessToken(fastify: FastifyInstance) { wallet, payload: { iss: await wallet.getAddress(), - sub: req.user.address, + sub: req.authentication.user.address, aud: config.authDomain, nbf: new Date(), // Set to expire in 100 years exp: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 100), iat: new Date(), - ctx: req.user.session, + ctx: req.authentication.user.session, }, }); diff --git a/src/server/routes/backend-wallet/lite/create.ts b/src/server/routes/backend-wallet/lite/create.ts index 37a39c70..85144252 100644 --- a/src/server/routes/backend-wallet/lite/create.ts +++ b/src/server/routes/backend-wallet/lite/create.ts @@ -12,6 +12,7 @@ import { } from "thirdweb/wallets/smart"; import { createSmartLocalWalletDetails } from "../../../utils/wallets/create-smart-wallet"; import { updateBackendWalletLiteAccess } from "../../../../shared/db/wallets/update-backend-wallet-lite-access"; +import { assertAuthenticationType } from "../../../utils/auth"; const requestSchema = Type.Object({ teamId: Type.String({ @@ -42,9 +43,7 @@ responseSchema.example = { }, }; -export const createBackendWalletLiteRoute = async ( - fastify: FastifyInstance, -) => { +export async function createBackendWalletLiteRoute(fastify: FastifyInstance) { fastify.withTypeProvider().route<{ Params: Static; Body: Static; @@ -66,17 +65,13 @@ export const createBackendWalletLiteRoute = async ( hide: true, }, handler: async (req, reply) => { - const dashboardUserAddress = checksumAddress(req.user.address); - if (!dashboardUserAddress) { - throw createCustomError( - "This endpoint must be called from the thirdweb dashboard.", - StatusCodes.FORBIDDEN, - "DASHBOARD_AUTH_REQUIRED", - ); - } + assertAuthenticationType(req, ["dashboard"]); const { teamId } = req.params; const { salt, litePassword } = req.body; + const dashboardUserAddress = checksumAddress( + req.authentication.user.address, + ); const liteAccess = await getBackendWalletLiteAccess({ teamId }); if ( @@ -86,15 +81,22 @@ export const createBackendWalletLiteRoute = async ( liteAccess.salt !== salt ) { throw createCustomError( - "The salt does not match the authenticated user. Try requesting a backend wallet again.", + "The salt does not exist for this user. Try requesting a backend wallet again.", StatusCodes.BAD_REQUEST, "INVALID_LITE_WALLET_SALT", ); } + if (liteAccess.accountAddress) { + throw createCustomError( + "A backend wallet already exists for this team.", + StatusCodes.BAD_REQUEST, + "LITE_WALLET_ALREADY_EXISTS", + ); + } // Generate a signer wallet and store the smart:local wallet, encrypted with `litePassword`. const walletDetails = await createSmartLocalWalletDetails({ - label: `${teamId} (${new Date()})`, + label: `${teamId} (${new Date().toISOString()})`, accountFactoryAddress: DEFAULT_ACCOUNT_FACTORY_V0_7, entrypointAddress: ENTRYPOINT_ADDRESS_v0_7, encryptionPassword: litePassword, @@ -120,4 +122,4 @@ export const createBackendWalletLiteRoute = async ( }); }, }); -}; +} diff --git a/src/server/routes/backend-wallet/lite/get.ts b/src/server/routes/backend-wallet/lite/get.ts index e24f7881..c9c59060 100644 --- a/src/server/routes/backend-wallet/lite/get.ts +++ b/src/server/routes/backend-wallet/lite/get.ts @@ -7,7 +7,7 @@ import { createBackendWalletLiteAccess } from "../../../../shared/db/wallets/cre import { getBackendWalletLiteAccess } from "../../../../shared/db/wallets/get-backend-wallet-lite-access"; import { AddressSchema } from "../../../schemas/address"; import { standardResponseSchema } from "../../../schemas/shared-api-schemas"; -import { createCustomError } from "../../../middleware/error"; +import { assertAuthenticationType } from "../../../utils/auth"; const requestSchema = Type.Object({ teamId: Type.String({ @@ -33,7 +33,7 @@ responseSchema.example = { }, }; -export const listBackendWalletsLiteRoute = async (fastify: FastifyInstance) => { +export async function listBackendWalletsLiteRoute(fastify: FastifyInstance) { fastify.withTypeProvider().route<{ Params: Static; Reply: Static; @@ -53,20 +53,16 @@ export const listBackendWalletsLiteRoute = async (fastify: FastifyInstance) => { hide: true, }, handler: async (req, reply) => { - const dashboardUserAddress = checksumAddress(req.user.address); - if (!dashboardUserAddress) { - throw createCustomError( - "This endpoint must be called from the thirdweb dashboard.", - StatusCodes.FORBIDDEN, - "DASHBOARD_AUTH_REQUIRED", - ); - } + assertAuthenticationType(req, ["dashboard"]); const { teamId } = req.params; const liteAccess = await getBackendWalletLiteAccess({ teamId }); + const dashboardUserAddress = checksumAddress( + req.authentication.user.address, + ); - // If a wallet exists, return it. - if (liteAccess?.accountAddress) { + // If a salt exists (even if the wallet isn't created yet), return it. + if (liteAccess) { return reply.status(StatusCodes.OK).send({ result: { walletAddress: liteAccess.accountAddress, @@ -91,4 +87,4 @@ export const listBackendWalletsLiteRoute = async (fastify: FastifyInstance) => { }); }, }); -}; +} diff --git a/src/server/routes/backend-wallet/sign-message.ts b/src/server/routes/backend-wallet/sign-message.ts index fa3bb0cf..b1e2b624 100644 --- a/src/server/routes/backend-wallet/sign-message.ts +++ b/src/server/routes/backend-wallet/sign-message.ts @@ -12,6 +12,7 @@ import { getChain } from "../../../shared/utils/chain"; import { createCustomError } from "../../middleware/error"; import { standardResponseSchema } from "../../schemas/shared-api-schemas"; import { walletHeaderSchema } from "../../schemas/wallet"; +import { getDecryptionPassword } from "../../utils/auth"; const requestBodySchema = Type.Object({ message: Type.String(), @@ -46,6 +47,7 @@ export async function signMessageRoute(fastify: FastifyInstance) { const { message, isBytes, chainId } = request.body; const { "x-backend-wallet-address": walletAddress } = request.headers as Static; + const decryptionPassword = getDecryptionPassword(request); if (isBytes && !isHex(message)) { throw createCustomError( diff --git a/src/server/utils/auth.ts b/src/server/utils/auth.ts new file mode 100644 index 00000000..c9e2cdc1 --- /dev/null +++ b/src/server/utils/auth.ts @@ -0,0 +1,27 @@ +import type { FastifyRequest } from "fastify"; +import type { AuthenticationType } from "../middleware/auth"; +import { createCustomError } from "../middleware/error"; +import { StatusCodes } from "http-status-codes"; +import { env } from "node:process"; + +export function assertAuthenticationType( + req: FastifyRequest, + types: T[], +): asserts req is FastifyRequest & { + authentication: { type: T }; +} { + if (!types.includes(req.authentication.type as T)) { + throw createCustomError( + `This endpoint requires authentication type: ${types.join(", ")}`, + StatusCodes.FORBIDDEN, + "FORBIDDEN_AUTHENTICATION_TYPE", + ); + } +} + +export function getDecryptionPassword(req: FastifyRequest) { + if (env.ENGINE_MODE === "lite" && req.authentication.type === "lite") { + return req.authentication.litePassword; + } + return env.ENCRYPTION_PASSWORD; +} diff --git a/src/shared/utils/account.ts b/src/shared/utils/account.ts index 496071e3..233140f6 100644 --- a/src/shared/utils/account.ts +++ b/src/shared/utils/account.ts @@ -29,7 +29,9 @@ export const getAccount = async (args: { const { chainId, from, accountAddress } = args; const chain = await getChain(chainId); - if (accountAddress) return getSmartWalletV5({ chain, accountAddress, from }); + if (accountAddress) { + return getSmartWalletV5({ chain, accountAddress, from }); + } // Get from cache. const cacheKey = getAccountCacheKey({ chainId, from, accountAddress }); diff --git a/src/shared/utils/env.ts b/src/shared/utils/env.ts index 3fa84dcc..4160ac8f 100644 --- a/src/shared/utils/env.ts +++ b/src/shared/utils/env.ts @@ -73,7 +73,7 @@ export const env = createEnv({ SEND_TRANSACTION_QUEUE_CONCURRENCY: z.coerce.number().default(200), CONFIRM_TRANSACTION_QUEUE_CONCURRENCY: z.coerce.number().default(200), ENGINE_MODE: z - .enum(["default", "sandbox", "server_only", "worker_only"]) + .enum(["default", "sandbox", "server_only", "worker_only", "lite"]) .default("default"), GLOBAL_RATE_LIMIT_PER_MIN: z.coerce.number().default(400 * 60), DD_TRACER_ACTIVATED: boolEnvSchema(false),