Skip to main content
A tiny composite TS package every other workspace depends on. Three files.

Layout

shared/
├── package.json                           # name: @darkpool/shared, dotenv + zod deps
├── tsconfig.json                          # composite: true
└── src/
    ├── index.ts                           # browser-safe barrel
    ├── env.ts                             # @darkpool/shared/env (server-only)
    ├── constants.ts                       # mirrored Move constants
    └── bytes.ts                           # hex/utf8 → byte vector helpers

index.ts

Browser-safe barrel. Re-exports bytes.js + constants.js + the Env type only. env.ts is not re-exported because it touches process.env/dotenv, which breaks browser bundles. Server-side code imports @darkpool/shared/env directly.

env.ts

zod schema for every .env var. Two entry points:
import { loadEnv } from '@darkpool/shared/env';

// strict: throws with a clean message on missing / malformed vars
const env = loadEnv();

// permissive: scripts that only touch a subset
const env = loadEnv({ permissive: true });
The result is cached, so subsequent calls don’t re-validate. Up-walks ancestors for .env, so it resolves correctly from any subdirectory. tsx watch from packages/server finds repo-root .env cleanly.

constants.ts

Mirrors Move-side enums:
export const Outcome = { YES: 1, NO: 2 } as const;
export const DUSDC_DECIMALS = 6;
export const SUI_CLOCK_OBJECT_ID = '0x0000000000000000000000000000000000000000000000000000000000000006';
Single source of truth that must stay in sync with Move constants. If Move’s Outcome::YES changes, this file changes too.

bytes.ts

export function hexToBytes(hex: string): Uint8Array;
export function utf8ToBytes(s: string): Uint8Array;
export function stripHexPrefix(hex: string): string;
Used by scripts (scripts/lib/cli.ts), server (indexer/ for ID parsing), and frontend (lib/ptb.ts for pure.vector('u8', …) args).