Skip to content
This repository was archived by the owner on Nov 21, 2025. It is now read-only.

Commit cd1abe2

Browse files
committed
feat: env on web or server, logic flow for fetching contract details from web
1 parent 72bbcb4 commit cd1abe2

23 files changed

Lines changed: 384 additions & 376 deletions

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"license": "MIT",
66
"type": "module",
77
"dependencies": {
8-
"tevm": "1.0.0-next.129",
8+
"tevm": "1.0.0-next.130",
99
"viem": "^2.23.8",
1010
"zod": "^3.21.4"
1111
},

packages/core/src/chains.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import { Common, mainnet } from "tevm/common";
22

33
import { parseEnv } from "./env";
44

5-
const env = parseEnv();
6-
75
/* ---------------------------------- TYPES --------------------------------- */
8-
export type Chain = Common & {
6+
export type ChainConfig = Common & {
97
rpcUrl: string;
108
etherscan?: {
119
apiUrl: string;
@@ -18,25 +16,27 @@ export type Chain = Common & {
1816
};
1917

2018
/* --------------------------------- CHAINS --------------------------------- */
21-
export const SUPPORTED_CHAINS = [
22-
{
23-
...mainnet,
24-
rpcUrl: env.MAINNET_RPC_URL,
25-
etherscan: {
26-
apiUrl: "https://api.etherscan.io/api",
27-
apiKey: env.MAINNET_ETHERSCAN_API_KEY,
28-
},
29-
blockscout: {
30-
apiUrl: "https://eth.blockscout.com/api",
31-
apiKey: env.MAINNET_BLOCKSCOUT_API_KEY,
32-
},
33-
},
34-
] as const satisfies Array<Chain>;
19+
export const SUPPORTED_CHAINS = [mainnet] as const satisfies Array<Common>;
20+
// Use this from the server only as this will use env variables
21+
export const getChainConfig = ({ chainId }: { chainId: number | string }) => {
22+
const env = parseEnv("server");
3523

36-
/* ---------------------------------- UTILS --------------------------------- */
37-
export const getChain = (chainId: number | string): Chain => {
3824
const chain = SUPPORTED_CHAINS.find((chain) => chain.id.toString() === chainId.toString());
3925
if (!chain) throw new Error("Chain not supported");
4026

41-
return chain;
27+
switch (chainId.toString()) {
28+
case mainnet.id.toString():
29+
return {
30+
...mainnet,
31+
rpcUrl: env ? env.MAINNET_RPC_URL : "",
32+
etherscan: {
33+
apiUrl: "https://api.etherscan.io/api",
34+
apiKey: env ? env.MAINNET_ETHERSCAN_API_KEY : "",
35+
},
36+
blockscout: {
37+
apiUrl: "https://eth.blockscout.com/api",
38+
apiKey: env.MAINNET_BLOCKSCOUT_API_KEY,
39+
},
40+
} as const satisfies ChainConfig;
41+
}
4242
};

packages/core/src/env.server.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { z } from "zod";
2+
3+
import { sharedEnvSchema } from "./env.shared";
4+
5+
export const serverEnvSchema = z.object({
6+
// Auth
7+
FRONTEND_URL: z.string().default("http://localhost:5173"), // for CORS in production
8+
COOKIE_SECRET: z.string().min(32).default("secret-cookie-mininum-32-chars-long"), // protect from xss
9+
SESSION_TTL: z.number().default(60 * 60 * 24), // 24 hours in seconds
10+
11+
// LLM (TODO: update to use LM Studio)
12+
DEEPINFRA_MODEL_URL: z.string().default("https://api.deepinfra.com/v1/inference/Qwen/QwQ-32B"),
13+
DEEPINFRA_API_KEY: z.string(),
14+
15+
// Cache
16+
DRAGONFLY_PORT: z.coerce.number().positive().default(6379),
17+
DEFAULT_CACHE_TIME: z.coerce
18+
.number()
19+
.positive()
20+
.default(60 * 60 * 24), // 24 hours in seconds
21+
22+
// API keys
23+
MAINNET_RPC_URL: z.string().default("https://eth.llamarpc.com"),
24+
MAINNET_ETHERSCAN_API_KEY: z.string().default(""),
25+
MAINNET_BLOCKSCOUT_API_KEY: z.string().default(""),
26+
});
27+
28+
export type ServerEnv = z.infer<typeof serverEnvSchema>;
29+
export type ServerEnvironment = z.infer<typeof sharedEnvSchema> & ServerEnv;

packages/core/src/env.shared.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { z } from "zod";
2+
3+
export const sharedEnvSchema = z.object({
4+
EXPOSED_NODE_ENV: z.enum(["local", "test", "production"]).default("local"),
5+
6+
// Server
7+
EXPOSED_SERVER_HOST_DEV: z.string().default("localhost"),
8+
EXPOSED_SERVER_PORT_DEV: z.coerce.number().positive().default(8888),
9+
});
10+
11+
export type SharedEnv = z.infer<typeof sharedEnvSchema>;

packages/core/src/env.ts

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,23 @@
1-
import { z, ZodError, ZodIntersection, ZodTypeAny } from "zod";
1+
import { z, ZodError } from "zod";
22

3-
const commonSchema = z.object({
4-
NODE_ENV: z.enum(["local", "test", "production"]).default("local"),
5-
6-
// Server & auth
7-
SERVER_HOST: z.string().default("localhost"),
8-
SERVER_PORT: z.coerce.number().positive().default(8888),
9-
FRONTEND_URL: z.string().default("http://localhost:5173"), // for CORS in production
10-
COOKIE_SECRET: z.string().min(32).default("secret-cookie-mininum-32-chars-long"), // protect from xss
11-
SESSION_TTL: z.number().default(60 * 60 * 24), // 24 hours in seconds
12-
13-
// LLM (TODO: update to use LM Studio)
14-
DEEPINFRA_MODEL_URL: z.string().default("https://api.deepinfra.com/v1/inference/Qwen/QwQ-32B"),
15-
DEEPINFRA_API_KEY: z.string(),
16-
17-
// Cache
18-
DRAGONFLY_PORT: z.coerce.number().positive().default(6379),
19-
DEFAULT_CACHE_TIME: z.coerce
20-
.number()
21-
.positive()
22-
.default(60 * 60 * 24), // 24 hours in seconds
23-
24-
// API keys
25-
MAINNET_RPC_URL: z.string().default("https://eth.llamarpc.com"),
26-
MAINNET_ETHERSCAN_API_KEY: z.string().default(""),
27-
MAINNET_BLOCKSCOUT_API_KEY: z.string().default(""),
28-
});
29-
30-
/**
31-
* Parses environment variables without validation or process exit Useful for client-side code that needs env defaults
32-
* but not validation
33-
*/
34-
export function parseEnv<TSchema extends ZodTypeAny | undefined = undefined>(
35-
schema?: TSchema,
36-
env: Record<string, unknown> = {},
37-
): z.infer<TSchema extends ZodTypeAny ? ZodIntersection<typeof commonSchema, TSchema> : typeof commonSchema> {
38-
const envSchema = schema !== undefined ? z.intersection(commonSchema, schema) : commonSchema;
39-
try {
40-
return envSchema.safeParse(env);
41-
} catch (error) {
42-
// Return defaults for any missing values
43-
return envSchema.parse({});
44-
}
45-
}
3+
import { ServerEnv, serverEnvSchema } from "./env.server";
4+
import { SharedEnv, sharedEnvSchema } from "./env.shared";
5+
import { WebEnv, webEnvSchema } from "./env.web";
466

477
/**
488
* Validates environment variables and exits process if invalid Used by server-side code that requires proper env
499
* configuration
5010
*/
51-
export function validateEnv<TSchema extends ZodTypeAny | undefined = undefined>(
52-
schema?: TSchema,
53-
): z.infer<TSchema extends ZodTypeAny ? ZodIntersection<typeof commonSchema, TSchema> : typeof commonSchema> {
54-
const envSchema = schema !== undefined ? z.intersection(commonSchema, schema) : commonSchema;
11+
export function parseEnv<T extends "web" | "server" = "server">(
12+
type?: T,
13+
): T extends "web" ? SharedEnv & WebEnv : SharedEnv & ServerEnv {
14+
const envSchema =
15+
type === "web" ? z.intersection(sharedEnvSchema, webEnvSchema) : z.intersection(sharedEnvSchema, serverEnvSchema);
16+
5517
try {
56-
return envSchema.parse(process.env);
18+
// @ts-expect-error Property env doesn't exist in import.meta
19+
const envSource = type === "web" ? import.meta.env : process.env;
20+
return envSchema.parse(envSource) as any;
5721
} catch (error) {
5822
if (error instanceof ZodError) {
5923
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -64,3 +28,8 @@ export function validateEnv<TSchema extends ZodTypeAny | undefined = undefined>(
6428
throw error;
6529
}
6630
}
31+
32+
// Export types for convenience
33+
export type { SharedEnv } from "./env.shared";
34+
export type { WebEnv, WebEnvironment } from "./env.web";
35+
export type { ServerEnv, ServerEnvironment } from "./env.server";

packages/core/src/env.web.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { z } from "zod";
2+
3+
import { sharedEnvSchema } from "./env.shared";
4+
5+
export const webEnvSchema = z.object({
6+
EXPOSED_SERVER_PROD_URL: z.string().optional(),
7+
});
8+
9+
export type WebEnv = z.infer<typeof webEnvSchema>;
10+
export type WebEnvironment = z.infer<typeof sharedEnvSchema> & WebEnv;

packages/core/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "../../.tsconfigs/base.json",
2+
"extends": "../../.tsconfigs/web.json",
33
"include": ["src"],
44
"compilerOptions": {
55
"baseUrl": ".",

packages/server/Dockerfile.server

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
FROM node:21.4.0-bookworm-slim
2-
ENV NODE_ENV=production
2+
ENV EXPOSED_NODE_ENV=production
33

44
# install build dependencies for bigint-buffer (https://solana.stackexchange.com/questions/4077/bigint-failed-to-load-bindings-pure-js-will-be-used-try-npm-run-rebuild-whe)
55
RUN apt update \

packages/server/bin/server.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
44
import { applyWSSHandler } from "@trpc/server/adapters/ws";
55
import fastify from "fastify";
66

7-
import { validateEnv } from "@core/env";
7+
import { parseEnv } from "@core/env";
88
import { AppContext, AppRouter, createAppRouter } from "@server/app/router";
99
import { Service } from "@server/service";
1010

1111
// Validate environment variables
12-
export const env = validateEnv();
12+
export const env = parseEnv("server");
1313

1414
// @see https://fastify.dev/docs/latest/
1515
export const server = fastify({
@@ -23,7 +23,7 @@ await server.register(fastifyWebsocket);
2323

2424
// With CORS configuration to specify allowed origins
2525
await server.register(import("@fastify/cors"), {
26-
origin: env.NODE_ENV === "production" ? [env.FRONTEND_URL] : "*",
26+
origin: env.EXPOSED_NODE_ENV === "production" ? env.FRONTEND_URL : "http://localhost:5173",
2727
credentials: true,
2828
});
2929

@@ -33,7 +33,7 @@ await server.register(import("@fastify/cookie"), {
3333
hook: "onRequest",
3434
parseOptions: {
3535
httpOnly: true,
36-
secure: env.NODE_ENV === "production",
36+
secure: env.EXPOSED_NODE_ENV === "production",
3737
sameSite: "strict" as const,
3838
maxAge: env.SESSION_TTL,
3939
},
@@ -80,8 +80,8 @@ export const start = async () => {
8080
},
8181
});
8282

83-
await server.listen({ host: env.SERVER_HOST, port: env.SERVER_PORT });
84-
console.log(`Server listening on http://${env.SERVER_HOST}:${env.SERVER_PORT}`);
83+
await server.listen({ host: env.EXPOSED_SERVER_HOST_DEV, port: env.EXPOSED_SERVER_PORT_DEV });
84+
console.log(`Server listening on http://${env.EXPOSED_SERVER_HOST_DEV}:${env.EXPOSED_SERVER_PORT_DEV}`);
8585

8686
// Apply WebSocket handler
8787
applyWSSHandler({

packages/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"fastify": "^4.21.0",
3232
"nanoid": "^5.1.3",
3333
"redis": "^4.7.0",
34-
"tevm": "1.0.0-next.128",
34+
"tevm": "1.0.0-next.130",
3535
"viem": "^2.23.8",
3636
"ws": "^8.18.0",
3737
"zod": "^3.21.4"

0 commit comments

Comments
 (0)