@l.x/config 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1 +1,36 @@
1
- {"name":"@l.x/config","version":"1.0.3","description":"LX Exchange - config","main":"index.js","dependencies":{"@luxexchange/config":"1.0.3"}}
1
+ {
2
+ "name": "@l.x/config",
3
+ "version": "1.0.5",
4
+ "dependencies": {
5
+ "react-native-dotenv": "3.2.0",
6
+ "@luxfi/utilities": "^1.0.6"
7
+ },
8
+ "devDependencies": {
9
+ "@types/node": "22.13.1",
10
+ "@typescript/native-preview": "7.0.0-dev.20260311.1",
11
+ "depcheck": "1.4.7",
12
+ "eslint": "8.57.1",
13
+ "jsdom": "^26.1.0",
14
+ "typescript": "5.8.3",
15
+ "vitest": "^4.0.16",
16
+ "@luxfi/eslint-config": "^1.0.6"
17
+ },
18
+ "nx": {
19
+ "includedScripts": []
20
+ },
21
+ "main": "src/index.ts",
22
+ "private": false,
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "typecheck": "nx typecheck config",
26
+ "typecheck:tsgo": "nx typecheck:tsgo config",
27
+ "lint": "nx lint config",
28
+ "lint:fix": "nx lint:fix config",
29
+ "lint:biome": "nx lint:biome config",
30
+ "lint:biome:fix": "nx lint:biome:fix config",
31
+ "lint:eslint": "nx lint:eslint config",
32
+ "lint:eslint:fix": "nx lint:eslint:fix config",
33
+ "test": "vitest run",
34
+ "check:deps:usage": "nx check:deps:usage config"
35
+ }
36
+ }
package/project.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@l.x/config",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "pkgs/config/src",
5
+ "projectType": "library",
6
+ "tags": ["scope:config", "type:lib"],
7
+ "targets": {
8
+ "typecheck": {},
9
+ "typecheck:tsgo": {},
10
+ "lint:biome": {},
11
+ "lint:biome:fix": {},
12
+ "lint:eslint": {},
13
+ "lint:eslint:fix": {},
14
+ "lint": {},
15
+ "lint:fix": {},
16
+ "check:deps:usage": {}
17
+ }
18
+ }
package/src/brand.ts ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Runtime brand configuration for white-label exchange deployments.
3
+ * Zero baked-in brands — everything loaded from /config.json at runtime.
4
+ *
5
+ * How it works:
6
+ * 1. Default config.json ships in the Docker image (Lux defaults)
7
+ * 2. K8s mounts a ConfigMap over /config.json per deployment
8
+ * 3. SPA calls loadBrandConfig() before first render
9
+ * 4. All brand references use the `brand` export which updates in place
10
+ *
11
+ * For zoo.exchange: mount a ConfigMap with Zoo branding over /config.json
12
+ * For any L2: same image, different ConfigMap
13
+ *
14
+ * KMS integration: set KMS_BRAND_SECRET env var to load brand config from
15
+ * KMS (Infisical) instead of a ConfigMap. The secret should contain the
16
+ * full RuntimeConfig JSON. The /config.json endpoint in the serving layer
17
+ * should proxy to KMS when this env var is set.
18
+ */
19
+
20
+ /** Theme color overrides applied on top of the default dark/light themes */
21
+ export interface BrandTheme {
22
+ /** Primary accent color (buttons, links) */
23
+ accent1?: string
24
+ /** Background color */
25
+ surface1?: string
26
+ /** Secondary surface */
27
+ surface2?: string
28
+ /** Tertiary surface */
29
+ surface3?: string
30
+ /** Primary text color */
31
+ neutral1?: string
32
+ /** Secondary text color */
33
+ neutral2?: string
34
+ /** Success status color */
35
+ statusSuccess?: string
36
+ /** Critical/error status color */
37
+ statusCritical?: string
38
+ }
39
+
40
+ export interface BrandConfig {
41
+ name: string
42
+ title: string
43
+ description: string
44
+ /** Legal entity name for Terms/Privacy, e.g. "Lux Industries Inc." */
45
+ legalEntity: string
46
+ /** Wallet product name, e.g. "Zoo Wallet" or "Lux Wallet" */
47
+ walletName: string
48
+ /** Protocol product name, e.g. "Zoo Protocol" or "Lux Protocol" */
49
+ protocolName: string
50
+ /** Copyright holder name, e.g. "Zoo Labs Foundation" */
51
+ copyrightHolder: string
52
+ appDomain: string
53
+ docsDomain: string
54
+ infoDomain: string
55
+ gatewayDomain: string
56
+ wsDomain: string
57
+ helpUrl: string
58
+ termsUrl: string
59
+ privacyUrl: string
60
+ downloadUrl: string
61
+ complianceEmail: string
62
+ supportEmail: string
63
+ twitter: string
64
+ github: string
65
+ discord: string
66
+ logoUrl: string
67
+ faviconUrl: string
68
+ primaryColor: string
69
+ defaultChainId: number
70
+ supportedChainIds: number[]
71
+ walletConnectProjectId: string
72
+ insightsHost: string
73
+ insightsApiKey: string
74
+ /** Theme color overrides for dark and light modes */
75
+ theme?: {
76
+ light?: BrandTheme
77
+ dark?: BrandTheme
78
+ }
79
+ }
80
+
81
+ export interface RuntimeConfig {
82
+ brand: Partial<BrandConfig>
83
+ chains: {
84
+ defaultChainId: number
85
+ supported: number[]
86
+ }
87
+ rpc: Record<string, string>
88
+ api: {
89
+ graphql: string
90
+ gateway: string
91
+ insights: string
92
+ }
93
+ walletConnect: {
94
+ projectId: string
95
+ }
96
+ }
97
+
98
+ // Mutable brand — updated by loadBrandConfig() from /config.json
99
+ // Defaults are deliberately generic — override via config.json per deployment
100
+ export const brand: BrandConfig = {
101
+ name: '',
102
+ title: '',
103
+ description: '',
104
+ legalEntity: '',
105
+ walletName: '',
106
+ protocolName: '',
107
+ copyrightHolder: '',
108
+ appDomain: '',
109
+ docsDomain: '',
110
+ infoDomain: '',
111
+ gatewayDomain: '',
112
+ wsDomain: '',
113
+ helpUrl: '',
114
+ termsUrl: '',
115
+ privacyUrl: '',
116
+ downloadUrl: '',
117
+ complianceEmail: '',
118
+ supportEmail: '',
119
+ twitter: '',
120
+ github: '',
121
+ discord: '',
122
+ logoUrl: '',
123
+ faviconUrl: '/favicon.ico',
124
+ primaryColor: '#FC72FF',
125
+ defaultChainId: 1,
126
+ supportedChainIds: [],
127
+ walletConnectProjectId: '',
128
+ insightsHost: '',
129
+ insightsApiKey: '',
130
+ }
131
+
132
+ // Full runtime config (includes RPC, API endpoints, etc.)
133
+ export let runtimeConfig: RuntimeConfig | null = null
134
+
135
+ /**
136
+ * Load brand config from /config.json. Call once before React renders.
137
+ * The config.json is either the default shipped in the image, or a
138
+ * ConfigMap mounted by K8s for white-label deployments.
139
+ *
140
+ * KMS integration: when the serving layer sets KMS_BRAND_SECRET, it should
141
+ * proxy /config.json to fetch the secret value from KMS (Infisical). The
142
+ * SPA itself always fetches /config.json — KMS resolution is server-side.
143
+ */
144
+ export async function loadBrandConfig(): Promise<RuntimeConfig> {
145
+ try {
146
+ const res = await fetch('/config.json')
147
+ if (!res.ok) throw new Error(`${res.status}`)
148
+ const config: RuntimeConfig = await res.json()
149
+
150
+ // Apply brand overrides
151
+ if (config.brand) {
152
+ Object.assign(brand, config.brand)
153
+ }
154
+
155
+ // Derive convenience fields from name if not explicitly set
156
+ if (!brand.walletName && brand.name) {
157
+ brand.walletName = brand.name.replace(/\s*exchange\s*/i, '') + ' Wallet'
158
+ }
159
+ if (!brand.protocolName && brand.name) {
160
+ brand.protocolName = brand.name.replace(/\s*exchange\s*/i, '') + ' Protocol'
161
+ }
162
+ if (!brand.copyrightHolder) {
163
+ brand.copyrightHolder = brand.legalEntity
164
+ }
165
+
166
+ // Apply chain config
167
+ if (config.chains) {
168
+ brand.defaultChainId = config.chains.defaultChainId ?? brand.defaultChainId
169
+ brand.supportedChainIds = config.chains.supported ?? brand.supportedChainIds
170
+ }
171
+
172
+ // Apply walletconnect
173
+ if (config.walletConnect?.projectId) {
174
+ brand.walletConnectProjectId = config.walletConnect.projectId
175
+ }
176
+
177
+ // Apply analytics
178
+ if (config.api?.insights) {
179
+ brand.insightsHost = config.api.insights
180
+ }
181
+
182
+ // Update document title and favicon
183
+ if (typeof document !== 'undefined') {
184
+ if (config.brand?.title) {
185
+ document.title = config.brand.title
186
+ }
187
+ if (config.brand?.faviconUrl) {
188
+ const link = document.querySelector("link[rel*='icon']") as HTMLLinkElement | null
189
+ if (link) {
190
+ link.href = config.brand.faviconUrl
191
+ }
192
+ }
193
+ }
194
+
195
+ runtimeConfig = config
196
+ return config
197
+ } catch {
198
+ // Config fetch failed — use defaults (works for local dev)
199
+ return {
200
+ brand: {},
201
+ chains: { defaultChainId: brand.defaultChainId, supported: brand.supportedChainIds },
202
+ rpc: {},
203
+ api: { graphql: '', gateway: '', insights: brand.insightsHost },
204
+ walletConnect: { projectId: '' },
205
+ }
206
+ }
207
+ }
208
+
209
+ export function getBrandUrl(path: string): string {
210
+ return `https://${brand.appDomain}${path}`
211
+ }
212
+
213
+ export function getDocsUrl(path: string): string {
214
+ return `https://${brand.docsDomain}${path}`
215
+ }
216
+
217
+ export function getGatewayUrl(path: string): string {
218
+ return `https://${brand.gatewayDomain}${path}`
219
+ }
220
+
221
+ export function getWsUrl(path: string): string {
222
+ return `wss://${brand.wsDomain}${path}`
223
+ }
224
+
225
+ export function getRpcUrl(chainId: number): string | undefined {
226
+ return runtimeConfig?.rpc?.[String(chainId)]
227
+ }
228
+
229
+ export function getApiUrl(key: keyof RuntimeConfig['api']): string {
230
+ return runtimeConfig?.api?.[key] ?? ''
231
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Naming requirements for different environments:
3
+ * - Web ENV vars: must have process.env.REACT_APP_<var_name>
4
+ * - Extension ENV vars: must have process.env.<var_name>
5
+ * - Mobile ENV vars: must have BOTH process.env.<var_name> and <var_name>
6
+ *
7
+ * The CI requires web vars to have the required 'REACT_APP_' prefix. The react-dot-env library doesnt integrate with CI correctly,
8
+ * so we pull from github secrets directly with process.env.<var_name> for both extension and mobile. <var_name> is used for local mobile builds.
9
+ */
10
+
11
+ export interface Config {
12
+ alchemyApiKey: string
13
+ amplitudeProxyUrlOverride: string
14
+ apiBaseUrlOverride: string
15
+ apiBaseUrlV2Override: string
16
+ appsflyerApiKey: string
17
+ appsflyerAppId: string
18
+ blockaidProxyUrl: string
19
+ datadogClientToken: string
20
+ datadogProjectId: string
21
+ isE2ETest: boolean
22
+ forApiUrlOverride: string
23
+ graphqlUrlOverride: string
24
+ includePrototypeFeatures: string
25
+ infuraKey: string
26
+ isVercelEnvironment: boolean
27
+ jupiterProxyUrl: string
28
+ onesignalAppId: string
29
+ quicknodeEndpointName: string
30
+ quicknodeEndpointToken: string
31
+ scantasticApiUrlOverride: string
32
+ statsigProxyUrlOverride: string
33
+ statsigApiKey: string
34
+ tradingApiKey: string
35
+ tradingApiUrlOverride: string
36
+ tradingApiWebTestEnv: string
37
+ liquidityServiceUrlOverride: string
38
+ lxApiKey: string
39
+ unitagsApiUrlOverride: string
40
+ lxNotifApiBaseUrlOverride: string
41
+ entryGatewayApiUrlOverride: string
42
+ walletConnectProjectId: string
43
+ walletConnectProjectIdBeta: string
44
+ walletConnectProjectIdDev: string
45
+ enableSessionService: boolean
46
+ enableSessionUpgradeAuto: boolean
47
+ enableEntryGatewayProxy: boolean
48
+ }
@@ -0,0 +1,124 @@
1
+ import type { Config } from '@l.x/config/src/config-types'
2
+ import {
3
+ ALCHEMY_API_KEY,
4
+ AMPLITUDE_PROXY_URL_OVERRIDE,
5
+ API_BASE_URL_OVERRIDE,
6
+ API_BASE_URL_V2_OVERRIDE,
7
+ APPSFLYER_API_KEY,
8
+ APPSFLYER_APP_ID,
9
+ BLOCKAID_PROXY_URL,
10
+ DATADOG_CLIENT_TOKEN,
11
+ DATADOG_PROJECT_ID,
12
+ ENABLE_ENTRY_GATEWAY_PROXY,
13
+ ENABLE_SESSION_SERVICE,
14
+ ENABLE_SESSION_UPGRADE_AUTO,
15
+ ENTRY_GATEWAY_API_URL_OVERRIDE,
16
+ FOR_API_URL_OVERRIDE,
17
+ GRAPHQL_URL_OVERRIDE,
18
+ INCLUDE_PROTOTYPE_FEATURES,
19
+ INFURA_KEY,
20
+ IS_E2E_TEST,
21
+ JUPITER_PROXY_URL,
22
+ LIQUIDITY_SERVICE_URL_OVERRIDE,
23
+ ONESIGNAL_APP_ID,
24
+ QUICKNODE_ENDPOINT_NAME,
25
+ QUICKNODE_ENDPOINT_TOKEN,
26
+ SCANTASTIC_API_URL_OVERRIDE,
27
+ STATSIG_API_KEY,
28
+ STATSIG_PROXY_URL_OVERRIDE,
29
+ TRADING_API_KEY,
30
+ TRADING_API_URL_OVERRIDE,
31
+ LX_API_KEY,
32
+ LX_NOTIF_API_BASE_URL_OVERRIDE,
33
+ UNITAGS_API_URL_OVERRIDE,
34
+ WALLETCONNECT_PROJECT_ID,
35
+ WALLETCONNECT_PROJECT_ID_BETA,
36
+ WALLETCONNECT_PROJECT_ID_DEV,
37
+ } from 'react-native-dotenv'
38
+ import { isNonTestDev } from 'utilities/src/environment/constants'
39
+
40
+ // Module-level cache for config to avoid recomputing on every call
41
+ let cachedConfig: Config | undefined
42
+
43
+ // eslint-disable-next-line complexity
44
+ export const getConfig = (): Config => {
45
+ // Return cached config if already computed
46
+ if (cachedConfig !== undefined) {
47
+ return cachedConfig
48
+ }
49
+
50
+ /**
51
+ * Naming requirements for different environments:
52
+ * - Web ENV vars: must have process.env.REACT_APP_<var_name>
53
+ * - Extension ENV vars: must have process.env.<var_name>
54
+ * - Mobile ENV vars: must have BOTH process.env.<var_name> and <var_name>
55
+ *
56
+ * The CI requires web vars to have the required 'REACT_APP_' prefix. The react-dot-env library doesnt integrate with CI correctly,
57
+ * so we pull from github secrets directly with process.env.<var_name> for both extension and mobile. <var_name> is used for local mobile builds.
58
+ */
59
+
60
+ const config: Config = {
61
+ alchemyApiKey: process.env.REACT_APP_ALCHEMY_API_KEY || process.env.ALCHEMY_API_KEY || ALCHEMY_API_KEY,
62
+ amplitudeProxyUrlOverride: process.env.AMPLITUDE_PROXY_URL_OVERRIDE || AMPLITUDE_PROXY_URL_OVERRIDE,
63
+ apiBaseUrlOverride: process.env.API_BASE_URL_OVERRIDE || API_BASE_URL_OVERRIDE,
64
+ apiBaseUrlV2Override: process.env.API_BASE_URL_V2_OVERRIDE || API_BASE_URL_V2_OVERRIDE,
65
+ appsflyerApiKey: process.env.APPSFLYER_API_KEY || APPSFLYER_API_KEY,
66
+ appsflyerAppId: process.env.APPSFLYER_APP_ID || APPSFLYER_APP_ID,
67
+ blockaidProxyUrl: process.env.BLOCKAID_PROXY_URL || BLOCKAID_PROXY_URL,
68
+ datadogClientToken:
69
+ process.env.REACT_APP_DATADOG_CLIENT_TOKEN || process.env.DATADOG_CLIENT_TOKEN || DATADOG_CLIENT_TOKEN,
70
+ datadogProjectId: process.env.REACT_APP_DATADOG_PROJECT_ID || process.env.DATADOG_PROJECT_ID || DATADOG_PROJECT_ID,
71
+ enableEntryGatewayProxy: process.env.ENABLE_ENTRY_GATEWAY_PROXY === 'true' || ENABLE_ENTRY_GATEWAY_PROXY === 'true',
72
+ enableSessionService: process.env.ENABLE_SESSION_SERVICE === 'true' || ENABLE_SESSION_SERVICE === 'true',
73
+ enableSessionUpgradeAuto:
74
+ process.env.ENABLE_SESSION_UPGRADE_AUTO === 'true' || ENABLE_SESSION_UPGRADE_AUTO === 'true',
75
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
76
+ isE2ETest: process.env.IS_E2E_TEST?.toLowerCase() === 'true' || IS_E2E_TEST?.toLowerCase() === 'true',
77
+ forApiUrlOverride: process.env.FOR_API_URL_OVERRIDE || FOR_API_URL_OVERRIDE,
78
+ graphqlUrlOverride: process.env.GRAPHQL_URL_OVERRIDE || GRAPHQL_URL_OVERRIDE,
79
+ infuraKey: process.env.REACT_APP_INFURA_KEY || INFURA_KEY,
80
+ includePrototypeFeatures: process.env.INCLUDE_PROTOTYPE_FEATURES || INCLUDE_PROTOTYPE_FEATURES,
81
+ isVercelEnvironment: false, // never set to true for native
82
+ jupiterProxyUrl: process.env.JUPITER_PROXY_URL || JUPITER_PROXY_URL,
83
+ onesignalAppId: process.env.ONESIGNAL_APP_ID || ONESIGNAL_APP_ID,
84
+ quicknodeEndpointName:
85
+ process.env.REACT_APP_QUICKNODE_ENDPOINT_NAME || process.env.QUICKNODE_ENDPOINT_NAME || QUICKNODE_ENDPOINT_NAME,
86
+ quicknodeEndpointToken:
87
+ process.env.REACT_APP_QUICKNODE_ENDPOINT_TOKEN ||
88
+ process.env.QUICKNODE_ENDPOINT_TOKEN ||
89
+ QUICKNODE_ENDPOINT_TOKEN,
90
+ scantasticApiUrlOverride: process.env.SCANTASTIC_API_URL_OVERRIDE || SCANTASTIC_API_URL_OVERRIDE,
91
+ statsigApiKey: process.env.REACT_APP_STATSIG_API_KEY || process.env.STATSIG_API_KEY || STATSIG_API_KEY,
92
+ statsigProxyUrlOverride: process.env.STATSIG_PROXY_URL_OVERRIDE || STATSIG_PROXY_URL_OVERRIDE,
93
+ tradingApiKey: process.env.REACT_APP_TRADING_API_KEY || process.env.TRADING_API_KEY || TRADING_API_KEY,
94
+ tradingApiUrlOverride:
95
+ process.env.REACT_APP_TRADING_API_URL_OVERRIDE ||
96
+ process.env.TRADING_API_URL_OVERRIDE ||
97
+ TRADING_API_URL_OVERRIDE,
98
+ tradingApiWebTestEnv: process.env.REACT_APP_TRADING_API_TEST_ENV || '',
99
+ liquidityServiceUrlOverride:
100
+ process.env.REACT_APP_LIQUIDITY_SERVICE_URL_OVERRIDE ||
101
+ process.env.LIQUIDITY_SERVICE_URL_OVERRIDE ||
102
+ LIQUIDITY_SERVICE_URL_OVERRIDE,
103
+ lxApiKey: process.env.LX_API_KEY || LX_API_KEY,
104
+ unitagsApiUrlOverride: process.env.UNITAGS_API_URL_OVERRIDE || UNITAGS_API_URL_OVERRIDE,
105
+ lxNotifApiBaseUrlOverride:
106
+ process.env.LX_NOTIF_API_BASE_URL_OVERRIDE || LX_NOTIF_API_BASE_URL_OVERRIDE,
107
+ entryGatewayApiUrlOverride: process.env.ENTRY_GATEWAY_API_URL_OVERRIDE || ENTRY_GATEWAY_API_URL_OVERRIDE,
108
+ walletConnectProjectId:
109
+ process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID ||
110
+ process.env.WALLETCONNECT_PROJECT_ID ||
111
+ WALLETCONNECT_PROJECT_ID,
112
+ walletConnectProjectIdBeta: process.env.WALLETCONNECT_PROJECT_ID_BETA || WALLETCONNECT_PROJECT_ID_BETA,
113
+ walletConnectProjectIdDev: process.env.WALLETCONNECT_PROJECT_ID_DEV || WALLETCONNECT_PROJECT_ID_DEV,
114
+ }
115
+
116
+ if (isNonTestDev) {
117
+ // biome-ignore lint/suspicious/noConsole: Cannot use logger here, causes error from circular dep
118
+ console.debug('Using app config:', config)
119
+ }
120
+
121
+ // Cache and return frozen config
122
+ cachedConfig = Object.freeze(config)
123
+ return cachedConfig
124
+ }
@@ -0,0 +1,6 @@
1
+ import { Config } from '@l.x/config/src/config-types'
2
+ import { PlatformSplitStubError } from 'utilities/src/errors'
3
+
4
+ export function getConfig(): Config {
5
+ throw new PlatformSplitStubError('Use the correct getConfig for your platform')
6
+ }
@@ -0,0 +1,69 @@
1
+ import type { Config } from '@l.x/config/src/config-types'
2
+ import { isNonTestDev } from 'utilities/src/environment/constants'
3
+
4
+ // Module-level cache for config to avoid recomputing on every call
5
+ let cachedConfig: Config | undefined
6
+
7
+ // eslint-disable-next-line complexity
8
+ export const getConfig = (): Config => {
9
+ // Return cached config if already computed
10
+ if (cachedConfig !== undefined) {
11
+ return cachedConfig
12
+ }
13
+
14
+ /**
15
+ * Web-specific config implementation that uses process.env directly
16
+ * instead of react-native-dotenv to avoid Node.js filesystem dependencies
17
+ * in browser extension builds.
18
+ */
19
+ const config: Config = {
20
+ alchemyApiKey: process.env.REACT_APP_ALCHEMY_API_KEY || process.env.ALCHEMY_API_KEY || '',
21
+ amplitudeProxyUrlOverride: process.env.AMPLITUDE_PROXY_URL_OVERRIDE || '',
22
+ apiBaseUrlOverride: process.env.API_BASE_URL_OVERRIDE || '',
23
+ apiBaseUrlV2Override: process.env.API_BASE_URL_V2_OVERRIDE || '',
24
+ appsflyerApiKey: process.env.APPSFLYER_API_KEY || '',
25
+ appsflyerAppId: process.env.APPSFLYER_APP_ID || '',
26
+ blockaidProxyUrl: process.env.REACT_APP_BLOCKAID_PROXY_URL || process.env.BLOCKAID_PROXY_URL || '',
27
+ datadogClientToken: process.env.REACT_APP_DATADOG_CLIENT_TOKEN || process.env.DATADOG_CLIENT_TOKEN || '',
28
+ datadogProjectId: process.env.REACT_APP_DATADOG_PROJECT_ID || process.env.DATADOG_PROJECT_ID || '',
29
+ enableEntryGatewayProxy: process.env.VITE_ENABLE_ENTRY_GATEWAY_PROXY === 'true',
30
+ enableSessionService: process.env.ENABLE_SESSION_SERVICE === 'true',
31
+ enableSessionUpgradeAuto:
32
+ process.env.REACT_APP_ENABLE_SESSION_UPGRADE_AUTO === 'true' ||
33
+ process.env.ENABLE_SESSION_UPGRADE_AUTO === 'true',
34
+ isE2ETest: process.env.IS_E2E_TEST?.toLowerCase() === 'true',
35
+ forApiUrlOverride: process.env.FOR_API_URL_OVERRIDE || '',
36
+ graphqlUrlOverride: process.env.GRAPHQL_URL_OVERRIDE || '',
37
+ infuraKey: process.env.REACT_APP_INFURA_KEY || '',
38
+ includePrototypeFeatures: process.env.INCLUDE_PROTOTYPE_FEATURES || '',
39
+ isVercelEnvironment: process.env.VERCEL === '1',
40
+ jupiterProxyUrl: process.env.REACT_APP_JUPITER_PROXY_URL || process.env.JUPITER_PROXY_URL || '',
41
+ onesignalAppId: process.env.ONESIGNAL_APP_ID || '',
42
+ quicknodeEndpointName: process.env.REACT_APP_QUICKNODE_ENDPOINT_NAME || process.env.QUICKNODE_ENDPOINT_NAME || '',
43
+ quicknodeEndpointToken:
44
+ process.env.REACT_APP_QUICKNODE_ENDPOINT_TOKEN || process.env.QUICKNODE_ENDPOINT_TOKEN || '',
45
+ scantasticApiUrlOverride: process.env.SCANTASTIC_API_URL_OVERRIDE || '',
46
+ statsigApiKey: process.env.REACT_APP_STATSIG_API_KEY || process.env.STATSIG_API_KEY || '',
47
+ statsigProxyUrlOverride: process.env.STATSIG_PROXY_URL_OVERRIDE || '',
48
+ tradingApiKey: process.env.REACT_APP_TRADING_API_KEY || process.env.TRADING_API_KEY || '',
49
+ tradingApiUrlOverride: process.env.REACT_APP_TRADING_API_URL_OVERRIDE || process.env.TRADING_API_URL_OVERRIDE || '',
50
+ tradingApiWebTestEnv: process.env.REACT_APP_TRADING_API_TEST_ENV || '',
51
+ liquidityServiceUrlOverride:
52
+ process.env.REACT_APP_LIQUIDITY_SERVICE_URL_OVERRIDE || process.env.LIQUIDITY_SERVICE_URL_OVERRIDE || '',
53
+ lxApiKey: process.env.LX_API_KEY || '',
54
+ unitagsApiUrlOverride: process.env.UNITAGS_API_URL_OVERRIDE || '',
55
+ lxNotifApiBaseUrlOverride: process.env.LX_NOTIF_API_BASE_URL_OVERRIDE || '',
56
+ entryGatewayApiUrlOverride: process.env.ENTRY_GATEWAY_API_URL_OVERRIDE || '',
57
+ walletConnectProjectId:
58
+ process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID || process.env.WALLETCONNECT_PROJECT_ID || '',
59
+ walletConnectProjectIdBeta: process.env.WALLETCONNECT_PROJECT_ID_BETA || '',
60
+ walletConnectProjectIdDev: process.env.WALLETCONNECT_PROJECT_ID_DEV || '',
61
+ }
62
+ if (isNonTestDev) {
63
+ // biome-ignore lint/suspicious/noConsole: Cannot use logger here, causes error from circular dep
64
+ console.debug('Using app config:', config)
65
+ }
66
+ // Cache and return frozen config
67
+ cachedConfig = Object.freeze(config)
68
+ return cachedConfig
69
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export type { Config } from './config-types'
2
+ export { getConfig } from './getConfig'
3
+ export { brand, loadBrandConfig, getBrandUrl, getDocsUrl, getGatewayUrl, getWsUrl } from './brand'
4
+ export type { BrandConfig, BrandTheme } from './brand'
package/src/legal.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shared legal content for all Lux ecosystem apps.
3
+ *
4
+ * Every app (lux.exchange, zoo.exchange, pars.market, bridge.lux.network,
5
+ * explore.lux.network, etc.) serves /terms and /privacy using this content.
6
+ *
7
+ * The brand name is injected from the runtime brand config.
8
+ * The content is the same across all white-label deployments.
9
+ */
10
+
11
+ export const LEGAL_UPDATED = '2026-03-25'
12
+
13
+ export const LEGAL_URLS = {
14
+ terms: '/terms',
15
+ privacy: '/privacy',
16
+ regulatory: 'https://lps.lux.network/legal/regulatory-status',
17
+ compliance: 'mailto:compliance@lux.exchange',
18
+ legal: 'mailto:legal@lux.network',
19
+ privacyEmail: 'mailto:privacy@lux.network',
20
+ security: 'mailto:security@lux.network',
21
+ lp3103: 'https://lps.lux.network/docs/lp-3103-us-regulatory-classification',
22
+ lp3104: 'https://lps.lux.network/docs/lp-3104-genius-act-stablecoin-compliance',
23
+ }
24
+
25
+ /** Short disclaimer text for footers */
26
+ export const FOOTER_DISCLAIMER =
27
+ 'Experimental research software. Not legal, tax, or financial advice. Non-custodial — we never have access to your keys or funds. Use at your own risk.'
28
+
29
+ /** Regulatory notice for inline display (brand-agnostic) */
30
+ export const REGULATORY_NOTICE =
31
+ 'Native protocol tokens are classified as digital commodities under the SEC/CFTC joint interpretive release of March 17, 2026. ' +
32
+ 'Protocol staking, liquidity provision, and no-consideration airdrops are administrative activities outside the Howey test. ' +
33
+ 'This interface is experimental research software provided "as is" without warranty.'
34
+
35
+ /** Cookie/analytics notice text */
36
+ export const COOKIE_NOTICE =
37
+ 'This interface uses minimal cookies for functionality (theme, preferences) and privacy-respecting analytics. ' +
38
+ 'No tracking cookies. No behavioral profiling. No data sold to third parties.'
39
+
40
+ /** Non-custodial notice text */
41
+ export const NON_CUSTODIAL_NOTICE =
42
+ 'This protocol is fully non-custodial. We never have access to, custody of, or control over your private keys, ' +
43
+ 'seed phrases, passwords, or funds. All transactions are executed directly by you on the blockchain and are irreversible. ' +
44
+ 'Your keys, your assets, your responsibility.'
45
+
46
+ /**
47
+ * Generate brand-specific legal URLs
48
+ */
49
+ export function getLegalUrls(appDomain: string) {
50
+ return {
51
+ terms: `https://${appDomain}/terms`,
52
+ privacy: `https://${appDomain}/privacy`,
53
+ regulatory: LEGAL_URLS.regulatory,
54
+ }
55
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "extends": "../../config/tsconfig/app.json",
3
+ "include": [
4
+ "src/**/*.ts",
5
+ "src/**/*.tsx",
6
+ "src/**/*.json",
7
+ "env.native.d.ts",
8
+ "env.d.ts"
9
+ ],
10
+ "exclude": [
11
+ "src/**/*.spec.ts",
12
+ "src/**/*.spec.tsx",
13
+ "src/**/*.test.ts",
14
+ "src/**/*.test.tsx"
15
+ ],
16
+ "compilerOptions": {
17
+ "emitDeclarationOnly": true,
18
+ "noEmit": false,
19
+ "types": ["node"],
20
+ "paths": {
21
+ "@l.x/config/*": ["./src/*"]
22
+ }
23
+ },
24
+ "references": [
25
+ {
26
+ "path": "../utilities"
27
+ },
28
+ {
29
+ "path": "../eslint-config"
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "preserveSymlinks": true
5
+ },
6
+ "include": ["**/*.ts", "**/*.tsx", "**/*.json"],
7
+ "exclude": ["node_modules"]
8
+ }
@@ -0,0 +1,26 @@
1
+ import path from 'path'
2
+ import { defineConfig } from 'vitest/config'
3
+
4
+ export default defineConfig({
5
+ define: {
6
+ __DEV__: true,
7
+ },
8
+ test: {
9
+ globals: true,
10
+ environment: 'jsdom',
11
+ include: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
12
+ exclude: ['node_modules', 'dist'],
13
+ testTimeout: 15000,
14
+ reporters: ['verbose'],
15
+ coverage: {
16
+ include: ['src/**/*.ts*'],
17
+ exclude: ['src/**/*.d.ts'],
18
+ },
19
+ },
20
+ resolve: {
21
+ extensions: ['.web.ts', '.web.tsx', '.ts', '.tsx', '.js', '.jsx', '.json'],
22
+ alias: {
23
+ 'config/src': path.resolve(__dirname, './src'),
24
+ },
25
+ },
26
+ })
package/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from '@luxexchange/config';
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('@luxexchange/config');