UNPKG

@renegade-fi/core

Version:
241 lines (224 loc) 8.66 kB
import invariant from "tiny-invariant"; import { http, type Address, type Hex, type PublicClient, createPublicClient } from "viem"; import { arbitrumSepolia } from "viem/chains"; import { persist, subscribeWithSelector } from "zustand/middleware"; import { type Mutate, type StoreApi, createStore } from "zustand/vanilla"; import type { ChainId } from "./constants.js"; import type { ExternalConfig } from "./createExternalKeyConfig.js"; import { type Storage, createStorage, noopStorage } from "./createStorage.js"; import type { Evaluate, ExactPartial } from "./types/utils.js"; import type * as rustUtils from "./utils.d.ts"; import { AuthType } from "./utils/websocket.js"; export type CreateConfigParameters = { chainId: ChainId; darkPoolAddress: Address; priceReporterUrl: string; relayerUrl: string; httpPort?: number; pollingInterval?: number; ssr?: boolean | undefined; storage?: Storage | null | undefined; useInsecureTransport?: boolean; utils?: typeof rustUtils; websocketPort?: number; viemClient?: PublicClient; adminKey?: string; }; export function createConfig(parameters: CreateConfigParameters): InternalConfig { const { relayerUrl, priceReporterUrl, httpPort = 3000, pollingInterval = 5000, ssr, storage = createStorage({ storage: typeof window !== "undefined" && window.localStorage ? window.localStorage : noopStorage, }), useInsecureTransport = false, viemClient = createPublicClient({ chain: arbitrumSepolia, transport: http(), }), websocketPort = 4000, adminKey, } = parameters; invariant( parameters.utils, "Utils must be provided by the package if not supplied by the user.", ); ///////////////////////////////////////////////////////////////////////////////////////////////// // Create store ///////////////////////////////////////////////////////////////////////////////////////////////// function getInitialState(): State { return { seed: undefined, chainId: undefined, status: "disconnected", id: undefined, }; } const store = createStore( subscribeWithSelector( // only use persist middleware if storage exists storage ? persist(getInitialState, { name: "store", partialize(state) { // Only persist "critical" store properties to preserve storage size. return { id: state.id, seed: state.seed, chainId: state.chainId, status: state.status, } satisfies PartializedState; }, skipHydration: ssr, storage: storage as Storage<Record<string, unknown>>, }) : getInitialState, ), ); return { utils: parameters.utils, renegadeKeyType: "internal" as const, storage, relayerUrl, priceReporterUrl, darkPoolAddress: parameters.darkPoolAddress, getBaseUrl: (route = "") => { const protocol = useInsecureTransport || parameters.relayerUrl.includes("localhost") ? "http" : "https"; const baseUrl = parameters.relayerUrl.includes("localhost") ? `127.0.0.1:${httpPort}/v0` : `${parameters.relayerUrl}:${httpPort}/v0`; const formattedRoute = route.startsWith("/") ? route : `/${route}`; return `${protocol}://${baseUrl}${formattedRoute}`; }, getPriceReporterBaseUrl: () => { const baseUrl = parameters.priceReporterUrl.includes("localhost") ? `ws://127.0.0.1:${websocketPort}/` : `wss://${parameters.priceReporterUrl}:${websocketPort}/`; return baseUrl; }, getPriceReporterHTTPBaseUrl: (route = "") => { const baseUrl = parameters.priceReporterUrl.includes("localhost") ? `http://127.0.0.1:${httpPort}` : `https://${parameters.priceReporterUrl}:${httpPort}`; const formattedRoute = route.startsWith("/") ? route : `/${route}`; return `${baseUrl}${formattedRoute}`; }, getWebsocketBaseUrl: () => { const protocol = useInsecureTransport || parameters.relayerUrl.includes("localhost") ? "ws" : "wss"; const baseUrl = parameters.relayerUrl.includes("localhost") ? `127.0.0.1:${websocketPort}` : `${parameters.relayerUrl}:${websocketPort}`; return `${protocol}://${baseUrl}`; }, getSymmetricKey(type?: AuthType) { invariant(parameters.utils, "Utils are required"); if (type === AuthType.Admin) { invariant(parameters.adminKey, "Admin key is required"); const symmetricKey = parameters.utils.b64_to_hex_hmac_key( parameters.adminKey, ) as Hex; invariant(symmetricKey, "Admin key is required"); return symmetricKey; } const seed = store.getState().seed; invariant(seed, "Seed is required"); return parameters.utils.get_symmetric_key(seed) as Hex; }, pollingInterval, get state() { return store.getState(); }, setState(value) { let newState: State; if (typeof value === "function") newState = value(store.getState() as any); else newState = value; // Reset state if it got set to something not matching the base state const initialState = getInitialState(); if (typeof newState !== "object") newState = initialState; const isCorrupt = Object.keys(initialState).some((x) => !(x in newState)); if (isCorrupt) newState = initialState; store.setState(newState, true); }, subscribe(selector, listener, options) { return store.subscribe( selector as unknown as (state: State) => any, listener, options ? { ...options, fireImmediately: options.emitImmediately } : undefined, ); }, viemClient, adminKey, _internal: { store, ssr: Boolean(ssr), }, chainId: parameters.chainId, }; } export type BaseConfig = { utils: typeof rustUtils; getWebsocketBaseUrl: () => string; getBaseUrl: (route?: string) => string; getSymmetricKey: (type?: AuthType) => Hex; }; export type Config = BaseConfig & { chainId: ChainId; renegadeKeyType: "internal"; readonly storage: Storage | null; darkPoolAddress: Address; getPriceReporterBaseUrl: () => string; getPriceReporterHTTPBaseUrl: (route?: string) => string; pollingInterval: number; priceReporterUrl: string; relayerUrl: string; setState(value: State | ((state: State) => State)): void; state: State; subscribe<state>( selector: (state: State) => state, listener: (state: state, previousState: state) => void, options?: | { emitImmediately?: boolean | undefined; equalityFn?: ((a: state, b: state) => boolean) | undefined; } | undefined, ): () => void; viemClient: PublicClient; adminKey?: string; /** * Not part of versioned API, proceed with caution. * @internal */ _internal: { readonly store: Mutate<StoreApi<any>, [["zustand/persist", any]]>; readonly ssr: boolean; }; }; // For backwards-compatibility export type InternalConfig = Config; export type RenegadeConfig = InternalConfig | ExternalConfig; export interface State { seed?: Hex | undefined; chainId?: number; status?: "in relayer" | "disconnected" | "looking up" | "creating wallet" | "connecting"; id?: string | undefined; } // The type of keychain a config is using export const keyTypes = { EXTERNAL: "external", INTERNAL: "internal", NONE: "none", } as const; export type PartializedState = Evaluate< ExactPartial<Pick<State, "id" | "seed" | "chainId" | "status">> >;