@luxexchange/gating 1.0.0

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/.depcheckrc ADDED
@@ -0,0 +1,13 @@
1
+ ignores: [
2
+ # Standard ignores
3
+ "typescript",
4
+ "@typescript/native-preview",
5
+ "depcheck",
6
+
7
+ # Internal packages / workspaces
8
+ "@universe/gating",
9
+ "@universe/config",
10
+
11
+ # Peer dependencies
12
+ "react",
13
+ ]
package/.eslintrc.js ADDED
@@ -0,0 +1,21 @@
1
+ module.exports = {
2
+ extends: ['@luxfi/eslint-config/lib'],
3
+ parserOptions: {
4
+ tsconfigRootDir: __dirname,
5
+ },
6
+ overrides: [
7
+ {
8
+ files: ['*.ts', '*.tsx'],
9
+ rules: {
10
+ 'no-relative-import-paths/no-relative-import-paths': [
11
+ 'error',
12
+ {
13
+ allowSameFolder: false,
14
+ prefix: '@luxfi/gating',
15
+ },
16
+ ],
17
+ '@typescript-eslint/prefer-enum-initializers': 'off',
18
+ },
19
+ },
20
+ ],
21
+ }
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @universe/gating
2
+
3
+ // TODO
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@luxexchange/gating",
3
+ "version": "1.0.0",
4
+ "dependencies": {
5
+ "@statsig/client-core": "3.12.2",
6
+ "@statsig/js-client": "3.12.2",
7
+ "@statsig/js-local-overrides": "3.12.2",
8
+ "@statsig/react-bindings": "3.12.2",
9
+ "@statsig/react-native-bindings": "3.12.2",
10
+ "@luxexchange/api": "^1.0.0",
11
+ "@luxfi/utilities": "^1.0.0"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "22.13.1",
15
+ "@typescript/native-preview": "7.0.0-dev.20260108.1",
16
+ "@luxfi/eslint-config": "^1.0.0",
17
+ "depcheck": "1.4.7",
18
+ "eslint": "8.57.1",
19
+ "typescript": "5.8.3"
20
+ },
21
+ "scripts": {
22
+ "typecheck": "nx typecheck gating",
23
+ "typecheck:tsgo": "nx typecheck:tsgo gating",
24
+ "lint": "nx lint gating",
25
+ "lint:fix": "nx lint:fix gating",
26
+ "lint:biome": "nx lint:biome gating",
27
+ "lint:biome:fix": "nx lint:biome:fix gating",
28
+ "lint:eslint": "nx lint:eslint gating",
29
+ "lint:eslint:fix": "nx lint:eslint:fix gating",
30
+ "check:deps:usage": "nx check:deps:usage gating"
31
+ },
32
+ "nx": {
33
+ "includedScripts": []
34
+ },
35
+ "main": "src/index.ts",
36
+ "private": false,
37
+ "sideEffects": false
38
+ }
package/project.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@luxfi/gating",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "pkgs/gating/src",
5
+ "projectType": "library",
6
+ "tags": ["scope:gating", "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
+ }
@@ -0,0 +1,63 @@
1
+ import { LocalOverrideAdapter } from '@statsig/js-local-overrides'
2
+ import { getStatsigClient } from '@luxfi/gating/src/sdk/statsig'
3
+
4
+ // Workaround for @statsig 3.x.x refreshing client after applying overrides to get the result without reloading
5
+ // Should be removed after statsig add real time override apply functionality
6
+ // Adds refresh only to used LocalOverrideAdapter methods. Other methods need to be added if refresh is required.
7
+ export class LocalOverrideAdapterWrapper extends LocalOverrideAdapter {
8
+ constructor(sdkKey: string) {
9
+ super(sdkKey)
10
+ }
11
+
12
+ refreshStatsig(): void {
13
+ const statsigClient = getStatsigClient()
14
+ const statsigUser = statsigClient.getContext().user
15
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
16
+ statsigClient.updateUserAsync(statsigUser)
17
+ }
18
+
19
+ overrideGate(name: string, value: boolean): void {
20
+ super.overrideGate(name, value)
21
+ this.refreshStatsig()
22
+ }
23
+
24
+ removeGateOverride(name: string): void {
25
+ super.removeGateOverride(name)
26
+ this.refreshStatsig()
27
+ }
28
+
29
+ overrideDynamicConfig(name: string, value: Record<string, unknown>): void {
30
+ super.overrideDynamicConfig(name, value)
31
+ this.refreshStatsig()
32
+ }
33
+
34
+ removeDynamicConfigOverride(name: string): void {
35
+ super.removeDynamicConfigOverride(name)
36
+ this.refreshStatsig()
37
+ }
38
+
39
+ removeAllOverrides(): void {
40
+ super.removeAllOverrides()
41
+ this.refreshStatsig()
42
+ }
43
+
44
+ removeExperimentOverride(name: string): void {
45
+ super.removeExperimentOverride(name)
46
+ this.refreshStatsig()
47
+ }
48
+
49
+ overrideExperiment(name: string, value: Record<string, unknown>): void {
50
+ super.overrideExperiment(name, value)
51
+ this.refreshStatsig()
52
+ }
53
+
54
+ overrideLayer(name: string, value: Record<string, unknown>): void {
55
+ super.overrideLayer(name, value)
56
+ this.refreshStatsig()
57
+ }
58
+
59
+ removeLayerOverride(name: string): void {
60
+ super.removeLayerOverride(name)
61
+ this.refreshStatsig()
62
+ }
63
+ }
package/src/configs.ts ADDED
@@ -0,0 +1,289 @@
1
+ import { GasStrategy } from '@luxfi/api'
2
+
3
+ // TODO: move to own package
4
+ export enum Locale {
5
+ Afrikaans = 'af-ZA',
6
+ ArabicSaudi = 'ar-SA',
7
+ Catalan = 'ca-ES',
8
+ ChineseSimplified = 'zh-Hans',
9
+ ChineseTraditional = 'zh-Hant',
10
+ CzechCzechia = 'cs-CZ',
11
+ DanishDenmark = 'da-DK',
12
+ DutchNetherlands = 'nl-NL',
13
+ EnglishUnitedStates = 'en-US',
14
+ FinnishFinland = 'fi-FI',
15
+ FrenchFrance = 'fr-FR',
16
+ GreekGreece = 'el-GR',
17
+ HebrewIsrael = 'he-IL',
18
+ HindiIndia = 'hi-IN',
19
+ HungarianHungarian = 'hu-HU',
20
+ IndonesianIndonesia = 'id-ID',
21
+ ItalianItaly = 'it-IT',
22
+ JapaneseJapan = 'ja-JP',
23
+ KoreanKorea = 'ko-KR',
24
+ MalayMalaysia = 'ms-MY',
25
+ NorwegianNorway = 'no-NO',
26
+ PolishPoland = 'pl-PL',
27
+ PortugueseBrazil = 'pt-BR',
28
+ PortuguesePortugal = 'pt-PT',
29
+ RomanianRomania = 'ro-RO',
30
+ RussianRussia = 'ru-RU',
31
+ Serbian = 'sr-SP',
32
+ SpanishLatam = 'es-419',
33
+ SpanishBelize = 'es-BZ',
34
+ SpanishCuba = 'es-CU',
35
+ SpanishDominicanRepublic = 'es-DO',
36
+ SpanishGuatemala = 'es-GT',
37
+ SpanishHonduras = 'es-HN',
38
+ SpanishMexico = 'es-MX',
39
+ SpanishNicaragua = 'es-NI',
40
+ SpanishPanama = 'es-PA',
41
+ SpanishPeru = 'es-PE',
42
+ SpanishPuertoRico = 'es-PR',
43
+ SpanishElSalvador = 'es-SV',
44
+ SpanishUnitedStates = 'es-US',
45
+ SpanishArgentina = 'es-AR',
46
+ SpanishBolivia = 'es-BO',
47
+ SpanishChile = 'es-CL',
48
+ SpanishColombia = 'es-CO',
49
+ SpanishCostaRica = 'es-CR',
50
+ SpanishEcuador = 'es-EC',
51
+ SpanishSpain = 'es-ES',
52
+ SpanishParaguay = 'es-PY',
53
+ SpanishUruguay = 'es-UY',
54
+ SpanishVenezuela = 'es-VE',
55
+ SwahiliTanzania = 'sw-TZ',
56
+ SwedishSweden = 'sv-SE',
57
+ TurkishTurkey = 'tr-TR',
58
+ UkrainianUkraine = 'uk-UA',
59
+ UrduPakistan = 'ur-PK',
60
+ VietnameseVietnam = 'vi-VN',
61
+ }
62
+
63
+ /**
64
+ * Dynamic Configs
65
+ * These should match the dynamic config's `Config Name` on Statsig
66
+ */
67
+ export enum DynamicConfigs {
68
+ // Shared
69
+ Swap = 'swap_config',
70
+ NetworkRequests = 'network_requests',
71
+ Chains = 'chains',
72
+ SyncTransactionSubmissionChainIds = 'sync_transaction_submission_chain_ids',
73
+ BlockedAsyncSubmissionChainIds = 'blocked_async_submission_chain_ids',
74
+
75
+ // Wallet
76
+ HomeScreenExploreTokens = 'home_screen_explore_tokens',
77
+ ForceUpgrade = 'force_upgrade',
78
+ OnDeviceRecovery = 'on_device_recovery',
79
+ UwuLink = 'uwulink_config',
80
+ GasStrategies = 'gas_strategy',
81
+ DatadogSessionSampleRate = 'datadog_session_sample_rate',
82
+ DatadogIgnoredErrors = 'datadog_ignored_errors',
83
+ EmbeddedWalletConfig = 'embedded_wallet_config',
84
+ ExtensionBiometricUnlock = 'extension_biometric_unlock_config',
85
+
86
+ // Web
87
+ AstroChain = 'astro_chain',
88
+ BlockedNftCollections = 'blocked_nft_collections',
89
+ ExternallyConnectableExtension = 'externally_connectable_extension',
90
+ LPConfig = 'lp_config',
91
+ AllowedV4WethHookAddresses = 'allowed_v4_weth_hook_addresses',
92
+ OutageBannerChainId = 'outage_banner_chain_id',
93
+ VerifiedAuctions = 'verified_auctions',
94
+ }
95
+
96
+ // Config values go here for easy access
97
+
98
+ // Shared
99
+ export enum SwapConfigKey {
100
+ AverageL1BlockTimeMs = 'averageL1BlockTimeMs',
101
+ AverageL2BlockTimeMs = 'averageL2BlockTimeMs',
102
+ TradingApiSwapRequestMs = 'tradingApiSwapRequestMs',
103
+
104
+ MinAutoSlippageToleranceL2 = 'minAutoSlippageToleranceL2',
105
+
106
+ EthSwapMinGasAmount = 'ethSwapMinGasAmount',
107
+ EthSendMinGasAmount = 'ethSendMinGasAmount',
108
+ PolygonSwapMinGasAmount = 'polygonSwapMinGasAmount',
109
+ PolygonSendMinGasAmount = 'polygonSendMinGasAmount',
110
+ AvalancheSwapMinGasAmount = 'avalancheSwapMinGasAmount',
111
+ AvalancheSendMinGasAmount = 'avalancheSendMinGasAmount',
112
+ CeloSwapMinGasAmount = 'celoSwapMinGasAmount',
113
+ CeloSendMinGasAmount = 'celoSendMinGasAmount',
114
+ MonSwapMinGasAmount = 'monSwapMinGasAmount',
115
+ MonSendMinGasAmount = 'monSendMinGasAmount',
116
+ SolanaSwapMinGasAmount = 'solanaSwapMinGasAmount',
117
+ SolanaSendMinGasAmount = 'solanaSendMinGasAmount',
118
+ GenericL2SwapMinGasAmount = 'genericL2SwapMinGasAmount',
119
+ GenericL2SendMinGasAmount = 'genericL2SendMinGasAmount',
120
+
121
+ LowBalanceWarningGasPercentage = 'lowBalanceWarningGasPercentage',
122
+ }
123
+
124
+ export enum NetworkRequestsConfigKey {
125
+ BalanceMaxRefetchAttempts = 'balanceMaxRefetchAttempts',
126
+ }
127
+
128
+ export enum ChainsConfigKey {
129
+ OrderedChainIds = 'orderedChainIds',
130
+ NewChainIds = 'newChainIds',
131
+ }
132
+
133
+ // Wallet
134
+ export enum ForceUpgradeConfigKey {
135
+ Status = 'status',
136
+ Translations = 'translations',
137
+ }
138
+
139
+ export type ForceUpgradeStatus = 'recommended' | 'required' | 'not-required'
140
+
141
+ type SupportedLocale = `${Extract<Locale[keyof Locale], string>}`
142
+
143
+ type ContentMessage = {
144
+ title: string
145
+ description: string
146
+ }
147
+
148
+ export type ForceUpgradeTranslations = Record<SupportedLocale, ContentMessage>
149
+
150
+ export enum EmbeddedWalletConfigKey {
151
+ BaseUrl = 'baseUrl',
152
+ }
153
+
154
+ export enum ExtensionBiometricUnlockConfigKey {
155
+ EnableOnboardingEnrollment = 'enableOnboardingEnrollment',
156
+ EnableSettingsEnrollment = 'enableSettingsEnrollment',
157
+ EnableUnlocking = 'enableUnlocking',
158
+ }
159
+
160
+ export enum SyncTransactionSubmissionChainIdsConfigKey {
161
+ ChainIds = 'chainIds',
162
+ }
163
+
164
+ export enum BlockedAsyncSubmissionChainIdsConfigKey {
165
+ ChainIds = 'chainIds',
166
+ }
167
+
168
+ export enum HomeScreenExploreTokensConfigKey {
169
+ EthChainId = 'ethChainId',
170
+ Tokens = 'tokens',
171
+ }
172
+
173
+ export enum OnDeviceRecoveryConfigKey {
174
+ AppLoadingTimeoutMs = 'appLoadingTimeoutMs',
175
+ MaxMnemonicsToLoad = 'maxMnemonicsToLoad',
176
+ }
177
+
178
+ export enum UwuLinkConfigKey {
179
+ Allowlist = 'allowlist',
180
+ }
181
+
182
+ export enum DatadogIgnoredErrorsConfigKey {
183
+ Errors = 'errors',
184
+ }
185
+
186
+ export enum DatadogSessionSampleRateKey {
187
+ Rate = 'rate',
188
+ }
189
+
190
+ export enum BlockedNftCollectionsConfigKey {
191
+ BlocklistedCollections = 'blocklistedCollections',
192
+ }
193
+
194
+ export enum ExternallyConnectableExtensionConfigKey {
195
+ ExtensionId = 'extensionId',
196
+ }
197
+
198
+ export type DatadogIgnoredErrorsValType = Array<{ messageContains: string; sampleRate: number }>
199
+
200
+ export type DatadogSessionSampleRateValType = number
201
+
202
+ export type GasStrategyType = 'general' | 'swap'
203
+
204
+ export type GasStrategyConditions = {
205
+ name: string
206
+ chainId: number
207
+ types: GasStrategyType
208
+ isActive: boolean
209
+ }
210
+
211
+ export type GasStrategyWithConditions = {
212
+ strategy: GasStrategy
213
+ conditions: GasStrategyConditions
214
+ }
215
+
216
+ export type GasStrategies = {
217
+ strategies: GasStrategyWithConditions[]
218
+ }
219
+
220
+ // Web
221
+ export enum QuickRouteChainsConfigKey {
222
+ Chains = 'quick_route_chains',
223
+ }
224
+
225
+ export enum AstroChainConfigKey {
226
+ Url = 'url',
227
+ }
228
+
229
+ export enum LPConfigKey {
230
+ DefaultSlippage = 'defaultSlippage',
231
+ V4SlippageOverride = 'v4SlippageOverride',
232
+ }
233
+
234
+ export enum AllowedV4WethHookAddressesConfigKey {
235
+ HookAddresses = 'hookAddresses',
236
+ }
237
+
238
+ export enum VerifiedAuctionsConfigKey {
239
+ VerifiedAuctionIds = 'verifiedAuctionIds',
240
+ }
241
+
242
+ export enum OutageBannerChainIdConfigKey {
243
+ ChainId = 'chainId',
244
+ }
245
+
246
+ export type DynamicConfigKeys = {
247
+ // Shared
248
+ [DynamicConfigs.Swap]: SwapConfigKey
249
+ [DynamicConfigs.NetworkRequests]: NetworkRequestsConfigKey
250
+ [DynamicConfigs.Chains]: ChainsConfigKey
251
+
252
+ // Wallet
253
+ [DynamicConfigs.HomeScreenExploreTokens]: HomeScreenExploreTokensConfigKey
254
+ [DynamicConfigs.ForceUpgrade]: ForceUpgradeConfigKey
255
+ [DynamicConfigs.OnDeviceRecovery]: OnDeviceRecoveryConfigKey
256
+ [DynamicConfigs.UwuLink]: UwuLinkConfigKey
257
+ [DynamicConfigs.DatadogIgnoredErrors]: DatadogIgnoredErrorsConfigKey
258
+ [DynamicConfigs.DatadogSessionSampleRate]: DatadogSessionSampleRateKey
259
+ [DynamicConfigs.EmbeddedWalletConfig]: EmbeddedWalletConfigKey
260
+ [DynamicConfigs.ExtensionBiometricUnlock]: ExtensionBiometricUnlockConfigKey
261
+ [DynamicConfigs.SyncTransactionSubmissionChainIds]: SyncTransactionSubmissionChainIdsConfigKey
262
+
263
+ // Web
264
+ [DynamicConfigs.AstroChain]: AstroChainConfigKey
265
+ [DynamicConfigs.BlockedNftCollections]: BlockedNftCollectionsConfigKey
266
+ [DynamicConfigs.ExternallyConnectableExtension]: ExternallyConnectableExtensionConfigKey
267
+ [DynamicConfigs.LPConfig]: LPConfigKey
268
+ [DynamicConfigs.AllowedV4WethHookAddresses]: AllowedV4WethHookAddressesConfigKey
269
+ [DynamicConfigs.VerifiedAuctions]: VerifiedAuctionsConfigKey
270
+ [DynamicConfigs.BlockedAsyncSubmissionChainIds]: BlockedAsyncSubmissionChainIdsConfigKey
271
+ [DynamicConfigs.OutageBannerChainId]: OutageBannerChainIdConfigKey
272
+ }
273
+
274
+ // This type must match the format in statsig dynamic config for uwulink
275
+ // https://console.statsig.com/5HjUux4OvSGzgqWIfKFt8i/dynamic_configs/uwulink_config
276
+ export type UwULinkAllowlistItem = {
277
+ chainId: number
278
+ address: string
279
+ name: string
280
+ logo?: {
281
+ dark?: string
282
+ light?: string
283
+ }
284
+ }
285
+
286
+ export type UwULinkAllowlist = {
287
+ contracts: UwULinkAllowlistItem[]
288
+ tokenRecipients: UwULinkAllowlistItem[]
289
+ }
@@ -0,0 +1,4 @@
1
+ export enum StatsigCustomAppValue {
2
+ Mobile = 'mobile',
3
+ Extension = 'extension',
4
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Experiment parameter names. Ordered alphabetically.
3
+ *
4
+ * These must match parameter names on Statsig within an experiment
5
+ */
6
+ export enum Experiments {
7
+ EthAsErc20DEX = 'eth_as_erc20_luxx_experiment',
8
+ ExploreBackendSorting = 'explore_backend_sorting',
9
+ NativeTokenPercentageBuffer = 'lp_native_buffer',
10
+ PriceUxUpdate = 'price_ux_update',
11
+ PrivateRpc = 'private_rpc',
12
+ SwapConfirmation = 'swap-confirmation',
13
+ UnichainFlashblocksModal = 'unichain_flashblocks_modal',
14
+ }
15
+
16
+ export enum Layers {
17
+ ExplorePage = 'explore-page',
18
+ SwapPage = 'swap-page',
19
+ }
20
+
21
+ // experiment groups
22
+
23
+ export enum NativeTokenPercentageBufferExperimentGroup {
24
+ Control = 'Control',
25
+ Buffer1 = 'Buffer1',
26
+ }
27
+
28
+ // experiment properties
29
+
30
+ export enum ArbitrumXV2SamplingProperties {
31
+ RoutingType = 'routingType',
32
+ }
33
+
34
+ export enum PrivateRpcProperties {
35
+ FlashbotsEnabled = 'flashbots_enabled',
36
+ RefundPercent = 'refund_percent',
37
+ }
38
+
39
+ export enum NativeTokenPercentageBufferProperties {
40
+ BufferSize = 'bufferSize',
41
+ }
42
+
43
+ export enum SwapConfirmationProperties {
44
+ WaitTimes = 'wait_times',
45
+ }
46
+
47
+ export enum ExploreBackendSortingProperties {
48
+ BackendSortingEnabled = 'backendSortingEnabled',
49
+ }
50
+
51
+ // Swap Layer experiment properties
52
+
53
+ export enum SwapLayerProperties {
54
+ UpdatedPriceUX = 'updatedPriceUX',
55
+ FlashblocksModalEnabled = 'flashblocksModalEnabled',
56
+ EthAsErc20DEXEnabled = 'ethAsErc20DEXEnabled',
57
+ MinEthErc20USDValueThresholdByChain = 'minEthErc20USDValueThresholdByChain',
58
+ }
59
+
60
+ export enum PriceUxUpdateProperties {
61
+ UpdatedPriceUX = SwapLayerProperties.UpdatedPriceUX,
62
+ }
63
+
64
+ export enum UnichainFlashblocksProperties {
65
+ FlashblocksModalEnabled = SwapLayerProperties.FlashblocksModalEnabled,
66
+ }
67
+
68
+ export enum EthAsErc20DEXProperties {
69
+ EthAsErc20DEXEnabled = SwapLayerProperties.EthAsErc20DEXEnabled,
70
+ MinEthErc20USDValueThresholdByChain = SwapLayerProperties.MinEthErc20USDValueThresholdByChain,
71
+ }
72
+
73
+ // Ordered alphabetically.
74
+ export type ExperimentProperties = {
75
+ [Experiments.EthAsErc20DEX]: EthAsErc20DEXProperties
76
+ [Experiments.ExploreBackendSorting]: ExploreBackendSortingProperties
77
+ [Experiments.NativeTokenPercentageBuffer]: NativeTokenPercentageBufferProperties
78
+ [Experiments.PriceUxUpdate]: PriceUxUpdateProperties
79
+ [Experiments.PrivateRpc]: PrivateRpcProperties
80
+ [Experiments.SwapConfirmation]: SwapConfirmationProperties
81
+ [Experiments.UnichainFlashblocksModal]: UnichainFlashblocksProperties
82
+ }
83
+
84
+ // will be a spread of all experiment properties in that layer
85
+ export const LayerProperties: Record<Layers, string[]> = {
86
+ [Layers.ExplorePage]: Object.values({
87
+ ...ExploreBackendSortingProperties,
88
+ }),
89
+ [Layers.SwapPage]: Object.values(SwapLayerProperties),
90
+ }
package/src/flags.ts ADDED
@@ -0,0 +1,234 @@
1
+ import { logger } from '@luxfi/utilities/src/logger/logger'
2
+ import { isWebApp } from '@luxfi/utilities/src/platform'
3
+
4
+ // only disable for this enum
5
+ /**
6
+ * Feature flag names.
7
+ * Add in alphabetical order for each section to decrease probability of merge conflicts.
8
+ */
9
+ /* biome-ignore-start lint/style/useEnumInitializers: preserve the order */
10
+ export enum FeatureFlags {
11
+ // Shared
12
+ AllowDEXOnlyRoutesInSwapSettings,
13
+ ArbitrumDutchV3,
14
+ BlockaidFotLogging,
15
+ BridgedAssetsBannerV2,
16
+ CentralizedPrices,
17
+ ChainedActions,
18
+ DisableSwap7702,
19
+ DisableSessionsForPlan,
20
+ EmbeddedWallet,
21
+ EnablePermitMismatchUX,
22
+ ForceDisableWalletGetCapabilities,
23
+ ForcePermitTransactions,
24
+ ForSessionsEnabled,
25
+ ForUrlMigration,
26
+ HashcashSolverEnabled,
27
+ Monad,
28
+ MultichainTokenUx,
29
+ NoLuxInterfaceFees,
30
+ PortionFields,
31
+ ProfitLoss,
32
+ SessionsPerformanceTrackingEnabled,
33
+ SessionsServiceEnabled,
34
+ SessionsUpgradeAutoEnabled,
35
+ SmartWallet,
36
+ SmartWalletDisableVideo,
37
+ Solana,
38
+ Soneium,
39
+ TurnstileSolverEnabled,
40
+ TwoSecondSwapQuotePollingInterval,
41
+ UnichainFlashblocks,
42
+ UniquoteEnabled,
43
+ UniroutePulumiEnabled,
44
+ LuxWrapped2025,
45
+ DEX,
46
+ DEXPriorityOrdersBase,
47
+ DEXPriorityOrdersOptimism,
48
+ DEXPriorityOrdersUnichain,
49
+ ViemProviderEnabled,
50
+ XLayer,
51
+
52
+ // Wallet
53
+ BlurredLockScreen,
54
+ BottomTabs,
55
+ BridgedAssetsBanner,
56
+ DisableFiatOnRampKorea,
57
+ Eip5792Methods,
58
+ EnableExportPrivateKeys,
59
+ EnableRestoreSeedPhrase,
60
+ EnableTransactionSpacingForDelegatedAccounts,
61
+
62
+ NotificationApiDataSource,
63
+ NotificationOnboardingCard,
64
+ NotificationService,
65
+
66
+ PrivateRpc,
67
+ Scantastic,
68
+ SelfReportSpamNFTs,
69
+ SmartWalletSettings,
70
+ UwULink,
71
+
72
+ // Web
73
+ AATestWeb,
74
+ BatchedSwaps,
75
+ ConversionApiMigration,
76
+ ConversionTracking,
77
+ DummyFlagTest,
78
+ GoogleConversionTracking,
79
+ GqlTokenLists,
80
+ LimitsFees,
81
+ LiquidityBatchedTransactions,
82
+ LpIncentives,
83
+ MigrateLiquidityApi,
84
+ NoLuxInterfaceFeesNotification,
85
+ PortfolioDefiTab,
86
+ PortfolioTokensAllocationChart,
87
+ PortoWalletConnector,
88
+ PriceRangeInputV2,
89
+ SolanaPromo,
90
+ TDPTokenCarousel,
91
+ Toucan,
92
+ ToucanAuctionKYC,
93
+ ToucanLaunchAuction,
94
+ TraceJsonRpc,
95
+ TwitterConversionTracking,
96
+ UnificationCopy,
97
+ UnirouteEnabled,
98
+ UniversalSwap,
99
+ }
100
+ /* biome-ignore-end lint/style/useEnumInitializers: preserve the order */
101
+
102
+ // These names must match the gate name on statsig.
103
+ // Add in alphabetical order to decrease probability of merge conflicts.
104
+ export const SHARED_FEATURE_FLAG_NAMES = new Map<FeatureFlags, string>([
105
+ [FeatureFlags.AllowDEXOnlyRoutesInSwapSettings, 'allow_luxx_only_routes_in_swap_settings'],
106
+ [FeatureFlags.ArbitrumDutchV3, 'luxx_dutchv3_orders_arbitrum'],
107
+ [FeatureFlags.BlockaidFotLogging, 'blockaid_fot_logging'],
108
+ [FeatureFlags.BridgedAssetsBannerV2, 'bridged_assets_banner_v2'],
109
+ [FeatureFlags.CentralizedPrices, 'centralized_prices'],
110
+ [FeatureFlags.ChainedActions, 'enable_chained_actions'],
111
+ [FeatureFlags.DisableSwap7702, 'disable-swap-7702'],
112
+ [FeatureFlags.DisableSessionsForPlan, 'disable_sessions_for_plan'],
113
+ [FeatureFlags.EmbeddedWallet, 'embedded_wallet'],
114
+ [FeatureFlags.EnablePermitMismatchUX, 'enable_permit2_mismatch_ux'],
115
+ [FeatureFlags.ForceDisableWalletGetCapabilities, 'force_disable_wallet_get_capabilities'],
116
+ [FeatureFlags.ForcePermitTransactions, 'force_permit_transactions'],
117
+ [FeatureFlags.ForSessionsEnabled, 'for_sessions_enabled'],
118
+ [FeatureFlags.ForUrlMigration, 'for_url_migration'],
119
+ [FeatureFlags.HashcashSolverEnabled, 'sessions_hashcash_solver_enabled'],
120
+ [FeatureFlags.Monad, 'monad'],
121
+ [FeatureFlags.MultichainTokenUx, 'multichain_token_ux'],
122
+ [FeatureFlags.NoLuxInterfaceFees, 'no_lux_interface_fees'],
123
+ [FeatureFlags.NotificationApiDataSource, 'notification_api_data_source'],
124
+ [FeatureFlags.PortionFields, 'portion-fields'],
125
+ [FeatureFlags.ProfitLoss, 'profit_loss'],
126
+ [FeatureFlags.SelfReportSpamNFTs, 'self-report-spam-nfts'],
127
+ [FeatureFlags.SessionsPerformanceTrackingEnabled, 'sessions_performance_tracking_enabled'],
128
+ [FeatureFlags.SessionsServiceEnabled, 'sessions_service_enabled'],
129
+ [FeatureFlags.SessionsUpgradeAutoEnabled, 'sessions_upgrade_auto_enabled'],
130
+ [FeatureFlags.SmartWallet, 'smart-wallet'],
131
+ [FeatureFlags.SmartWalletDisableVideo, 'smart_wallet_disable_video'],
132
+ [FeatureFlags.Solana, 'solana'],
133
+ [FeatureFlags.Soneium, 'soneium'],
134
+ [FeatureFlags.TurnstileSolverEnabled, 'sessions_turnstile_solver_enabled'],
135
+ [FeatureFlags.TwoSecondSwapQuotePollingInterval, 'two_second_swap_quote_polling_interval'],
136
+ [FeatureFlags.UnichainFlashblocks, 'unichain_flashblocks'],
137
+ [FeatureFlags.UniquoteEnabled, 'uniquote_enabled'],
138
+ [FeatureFlags.UnirouteEnabled, 'uniroute_rollout'],
139
+ [FeatureFlags.UniroutePulumiEnabled, 'uniroute_pulumi_enabled'],
140
+ [FeatureFlags.LuxWrapped2025, 'lux_wrapped_2025'],
141
+ [FeatureFlags.DEX, 'luxx'],
142
+ [FeatureFlags.DEXPriorityOrdersBase, 'luxx_priority_orders_base'],
143
+ [FeatureFlags.DEXPriorityOrdersOptimism, 'luxx_priority_orders_optimism'],
144
+ [FeatureFlags.DEXPriorityOrdersUnichain, 'luxx_priority_orders_unichain'],
145
+ [FeatureFlags.ViemProviderEnabled, 'viem_provider_enabled'],
146
+ [FeatureFlags.XLayer, 'x_layer'],
147
+ ])
148
+
149
+ // These names must match the gate name on statsig.
150
+ // Add in alphabetical order to decrease probability of merge conflicts.
151
+ export const WEB_FEATURE_FLAG_NAMES = new Map<FeatureFlags, string>([
152
+ ...SHARED_FEATURE_FLAG_NAMES,
153
+ [FeatureFlags.AATestWeb, 'aatest_web'],
154
+ [FeatureFlags.BatchedSwaps, 'batched_swaps'],
155
+ [FeatureFlags.ConversionApiMigration, 'conversion_api_migration'],
156
+ [FeatureFlags.ConversionTracking, 'conversion-tracking'],
157
+ [FeatureFlags.DummyFlagTest, 'dummy_flag_test'],
158
+ [FeatureFlags.GoogleConversionTracking, 'google_conversion_tracking'],
159
+ [FeatureFlags.GqlTokenLists, 'gql_token_lists'],
160
+ [FeatureFlags.LimitsFees, 'limits_fees'],
161
+ [FeatureFlags.LiquidityBatchedTransactions, 'liquidity_batched_transactions'],
162
+ [FeatureFlags.LpIncentives, 'lp_incentives'],
163
+ [FeatureFlags.MigrateLiquidityApi, 'migrate_liquidity_api'],
164
+ [FeatureFlags.NoLuxInterfaceFeesNotification, 'no_lux_interface_fees_notification'],
165
+ [FeatureFlags.PortfolioDefiTab, 'portfolio_defi_tab'],
166
+ [FeatureFlags.PortfolioTokensAllocationChart, 'portfolio_tokens_allocation_chart'],
167
+ [FeatureFlags.PortoWalletConnector, 'porto_wallet_connector'],
168
+ [FeatureFlags.PriceRangeInputV2, 'price_range_input_v2'],
169
+ [FeatureFlags.SolanaPromo, 'solana_promo'],
170
+ [FeatureFlags.TDPTokenCarousel, 'tdp_token_carousel'],
171
+ [FeatureFlags.Toucan, 'toucan'],
172
+ [FeatureFlags.ToucanAuctionKYC, 'toucan_auction_kyc'],
173
+ [FeatureFlags.ToucanLaunchAuction, 'toucan_launch_auction'],
174
+ [FeatureFlags.TraceJsonRpc, 'traceJsonRpc'],
175
+ [FeatureFlags.TwitterConversionTracking, 'twitter_conversion_tracking'],
176
+ [FeatureFlags.UnichainFlashblocks, 'unichain_flashblocks'],
177
+ [FeatureFlags.UnificationCopy, 'unification_copy'],
178
+ [FeatureFlags.UniversalSwap, 'universal_swap'],
179
+ ])
180
+
181
+ // These names must match the gate name on statsig.
182
+ // Add in alphabetical order to decrease probability of merge conflicts.
183
+ export const WALLET_FEATURE_FLAG_NAMES = new Map<FeatureFlags, string>([
184
+ ...SHARED_FEATURE_FLAG_NAMES,
185
+ [FeatureFlags.BlurredLockScreen, 'blurred_lock_screen'],
186
+ [FeatureFlags.BottomTabs, 'bottom_tabs'],
187
+ [FeatureFlags.BridgedAssetsBanner, 'bridged_assets_banner'],
188
+ [FeatureFlags.DisableFiatOnRampKorea, 'disable-fiat-onramp-korea'],
189
+ [FeatureFlags.Eip5792Methods, 'eip_5792_methods'],
190
+ [FeatureFlags.EnableExportPrivateKeys, 'enable-export-private-keys'],
191
+ [FeatureFlags.EnableRestoreSeedPhrase, 'enable-restore-seed-phrase'],
192
+ [FeatureFlags.EnableTransactionSpacingForDelegatedAccounts, 'enable_transaction_spacing_for_delegated_accounts'],
193
+
194
+ [FeatureFlags.NotificationOnboardingCard, 'notification_onboarding_card'],
195
+ [FeatureFlags.NotificationService, 'notification_system'],
196
+ [FeatureFlags.PrivateRpc, 'mev-blocker'],
197
+ [FeatureFlags.Scantastic, 'scantastic'],
198
+ [FeatureFlags.SmartWalletSettings, 'smart_wallet_settings'],
199
+ [FeatureFlags.UwULink, 'uwu-link'],
200
+ ])
201
+
202
+ export enum FeatureFlagClient {
203
+ Web = 0,
204
+ Wallet = 1,
205
+ }
206
+
207
+ const FEATURE_FLAG_NAMES = {
208
+ [FeatureFlagClient.Web]: WEB_FEATURE_FLAG_NAMES,
209
+ [FeatureFlagClient.Wallet]: WALLET_FEATURE_FLAG_NAMES,
210
+ }
211
+
212
+ export function getFeatureFlagName(flag: FeatureFlags, client?: FeatureFlagClient): string {
213
+ const names =
214
+ client !== undefined
215
+ ? FEATURE_FLAG_NAMES[client]
216
+ : isWebApp
217
+ ? FEATURE_FLAG_NAMES[FeatureFlagClient.Web]
218
+ : FEATURE_FLAG_NAMES[FeatureFlagClient.Wallet]
219
+ const name = names.get(flag)
220
+ if (!name) {
221
+ const err = new Error(`Feature ${FeatureFlags[flag]} does not have a name mapped for this application`)
222
+
223
+ logger.error(err, {
224
+ tags: {
225
+ file: 'flags.ts',
226
+ function: 'getFeatureFlagName',
227
+ },
228
+ })
229
+
230
+ throw err
231
+ }
232
+
233
+ return name
234
+ }
@@ -0,0 +1,12 @@
1
+ import { FeatureFlags } from '@luxexchange/gating/src/flags'
2
+ import { getFeatureFlag, useFeatureFlag } from '@luxexchange/gating/src/hooks'
3
+
4
+ function getIsHashcashSolverEnabled(): boolean {
5
+ return getFeatureFlag(FeatureFlags.HashcashSolverEnabled)
6
+ }
7
+
8
+ function useIsHashcashSolverEnabled(): boolean {
9
+ return useFeatureFlag(FeatureFlags.HashcashSolverEnabled)
10
+ }
11
+
12
+ export { getIsHashcashSolverEnabled, useIsHashcashSolverEnabled }
@@ -0,0 +1,19 @@
1
+ import { FeatureFlags } from '@luxexchange/gating/src/flags'
2
+ import { getFeatureFlag, useFeatureFlag } from '@luxexchange/gating/src/hooks'
3
+
4
+ /**
5
+ * Returns whether sessions performance tracking is enabled.
6
+ *
7
+ * Defaults to false (disabled) - must be explicitly enabled via Statsig flag.
8
+ * When enabled, session analytics will include duration measurements.
9
+ * When disabled, duration values will be -1 (sentinel value).
10
+ */
11
+ function getIsSessionsPerformanceTrackingEnabled(): boolean {
12
+ return getFeatureFlag(FeatureFlags.SessionsPerformanceTrackingEnabled)
13
+ }
14
+
15
+ function useIsSessionsPerformanceTrackingEnabled(): boolean {
16
+ return useFeatureFlag(FeatureFlags.SessionsPerformanceTrackingEnabled)
17
+ }
18
+
19
+ export { getIsSessionsPerformanceTrackingEnabled, useIsSessionsPerformanceTrackingEnabled }
@@ -0,0 +1,14 @@
1
+ import { getConfig } from '@luxfi/config'
2
+ import { FeatureFlags } from '@luxfi/gating/src/flags'
3
+ import { getFeatureFlag, useFeatureFlag } from '@luxfi/gating/src/hooks'
4
+
5
+ function getIsSessionServiceEnabled(): boolean {
6
+ return getConfig().enableSessionService || getFeatureFlag(FeatureFlags.SessionsServiceEnabled)
7
+ }
8
+
9
+ function useIsSessionServiceEnabled(): boolean {
10
+ const featureFlagEnabled = useFeatureFlag(FeatureFlags.SessionsServiceEnabled)
11
+ return getConfig().enableSessionService || featureFlagEnabled
12
+ }
13
+
14
+ export { getIsSessionServiceEnabled, useIsSessionServiceEnabled }
@@ -0,0 +1,9 @@
1
+ import { getConfig } from '@luxfi/config'
2
+ import { FeatureFlags } from '@luxfi/gating/src/flags'
3
+ import { getFeatureFlag } from '@luxfi/gating/src/hooks'
4
+
5
+ function getIsSessionUpgradeAutoEnabled(): boolean {
6
+ return getConfig().enableSessionUpgradeAuto || getFeatureFlag(FeatureFlags.SessionsUpgradeAutoEnabled)
7
+ }
8
+
9
+ export { getIsSessionUpgradeAutoEnabled }
@@ -0,0 +1,12 @@
1
+ import { FeatureFlags } from '@luxexchange/gating/src/flags'
2
+ import { getFeatureFlag, useFeatureFlag } from '@luxexchange/gating/src/hooks'
3
+
4
+ function getIsTurnstileSolverEnabled(): boolean {
5
+ return getFeatureFlag(FeatureFlags.TurnstileSolverEnabled)
6
+ }
7
+
8
+ function useIsTurnstileSolverEnabled(): boolean {
9
+ return useFeatureFlag(FeatureFlags.TurnstileSolverEnabled)
10
+ }
11
+
12
+ export { getIsTurnstileSolverEnabled, useIsTurnstileSolverEnabled }
@@ -0,0 +1,19 @@
1
+ import { isBetaEnv, isProdEnv } from '@luxfi/utilities/src/environment/env'
2
+ import { isWebApp } from '@luxfi/utilities/src/platform'
3
+
4
+ export enum StatsigEnvName {
5
+ Beta = 'beta', // mobile and extension environment-specific
6
+ Development = 'development',
7
+ Production = 'production',
8
+ Staging = 'staging', // interface (web) environment-specific
9
+ }
10
+
11
+ export function getStatsigEnvName(): StatsigEnvName {
12
+ if (isBetaEnv()) {
13
+ return isWebApp ? StatsigEnvName.Staging : StatsigEnvName.Beta
14
+ }
15
+ if (isProdEnv()) {
16
+ return StatsigEnvName.Production
17
+ }
18
+ return StatsigEnvName.Development
19
+ }
package/src/hooks.ts ADDED
@@ -0,0 +1,244 @@
1
+ import { StatsigClientEventCallback, StatsigLoadingStatus } from '@statsig/client-core'
2
+ import { DynamicConfigKeys } from '@luxfi/gating/src/configs'
3
+ import { ExperimentProperties, Experiments } from '@luxfi/gating/src/experiments'
4
+ import { FeatureFlags, getFeatureFlagName } from '@luxfi/gating/src/flags'
5
+ import {
6
+ getStatsigClient,
7
+ TypedReturn,
8
+ useDynamicConfig,
9
+ useExperiment,
10
+ useFeatureGate,
11
+ useGateValue,
12
+ useLayer,
13
+ useStatsigClient,
14
+ } from '@luxfi/gating/src/sdk/statsig'
15
+ import { useEffect, useMemo, useState } from 'react'
16
+ import { logger } from '@luxfi/utilities/src/logger/logger'
17
+
18
+ export function useFeatureFlag(flag: FeatureFlags): boolean {
19
+ const name = getFeatureFlagName(flag)
20
+ const value = useGateValue(name)
21
+ return value
22
+ }
23
+
24
+ export function useFeatureFlagWithLoading(flag: FeatureFlags): { value: boolean; isLoading: boolean } {
25
+ const { isStatsigLoading } = useStatsigClientStatus()
26
+ const name = getFeatureFlagName(flag)
27
+ const { value } = useFeatureGate(name)
28
+ return { value, isLoading: isStatsigLoading }
29
+ }
30
+
31
+ export function getFeatureFlag(flag: FeatureFlags): boolean {
32
+ try {
33
+ const name = getFeatureFlagName(flag)
34
+ return getStatsigClient().checkGate(name)
35
+ } catch (e) {
36
+ logger.debug('gating/hooks.ts', 'getFeatureFlag', JSON.stringify({ e }))
37
+ return false
38
+ }
39
+ }
40
+
41
+ export function useFeatureFlagWithExposureLoggingDisabled(flag: FeatureFlags): boolean {
42
+ const name = getFeatureFlagName(flag)
43
+ const value = useGateValue(name, { disableExposureLog: true })
44
+ return value
45
+ }
46
+
47
+ export function getFeatureFlagWithExposureLoggingDisabled(flag: FeatureFlags): boolean {
48
+ const name = getFeatureFlagName(flag)
49
+ return getStatsigClient().checkGate(name, { disableExposureLog: true })
50
+ }
51
+
52
+ export function useExperimentGroupNameWithLoading(experiment: Experiments): {
53
+ value: string | null
54
+ isLoading: boolean
55
+ } {
56
+ const { isStatsigLoading } = useStatsigClientStatus()
57
+ const statsigExperiment = useExperiment(experiment)
58
+ return { value: statsigExperiment.groupName, isLoading: isStatsigLoading }
59
+ }
60
+
61
+ export function useExperimentGroupName(experiment: Experiments): string | null {
62
+ const { groupName } = useExperiment(experiment)
63
+ return groupName
64
+ }
65
+
66
+ export function useExperimentValue<
67
+ Exp extends keyof ExperimentProperties,
68
+ Param extends ExperimentProperties[Exp],
69
+ ValType,
70
+ >({
71
+ experiment,
72
+ param,
73
+ defaultValue,
74
+ customTypeGuard,
75
+ }: {
76
+ experiment: Exp
77
+ param: Param
78
+ defaultValue: ValType
79
+ customTypeGuard?: (x: unknown) => x is ValType
80
+ }): ValType {
81
+ const statsigExperiment = useExperiment(experiment)
82
+ const value = statsigExperiment.get(param, defaultValue)
83
+ return checkTypeGuard({ value, defaultValue, customTypeGuard })
84
+ }
85
+
86
+ export function getExperimentValue<
87
+ Exp extends keyof ExperimentProperties,
88
+ Param extends ExperimentProperties[Exp],
89
+ ValType,
90
+ >({
91
+ experiment,
92
+ param,
93
+ defaultValue,
94
+ customTypeGuard,
95
+ }: {
96
+ experiment: Exp
97
+ param: Param
98
+ defaultValue: ValType
99
+ customTypeGuard?: (x: unknown) => x is ValType
100
+ }): ValType {
101
+ const statsigExperiment = getStatsigClient().getExperiment(experiment)
102
+ const value = statsigExperiment.get(param, defaultValue)
103
+ return checkTypeGuard({ value, defaultValue, customTypeGuard })
104
+ }
105
+
106
+ export function useExperimentValueWithExposureLoggingDisabled<
107
+ Exp extends keyof ExperimentProperties,
108
+ Param extends ExperimentProperties[Exp],
109
+ ValType,
110
+ >({
111
+ experiment,
112
+ param,
113
+ defaultValue,
114
+ customTypeGuard,
115
+ }: {
116
+ experiment: Exp
117
+ param: Param
118
+ defaultValue: ValType
119
+ customTypeGuard?: (x: unknown) => x is ValType
120
+ }): ValType {
121
+ const statsigExperiment = useExperiment(experiment, { disableExposureLog: true })
122
+ const value = statsigExperiment.get(param, defaultValue)
123
+ return checkTypeGuard({ value, defaultValue, customTypeGuard })
124
+ }
125
+
126
+ export function useDynamicConfigValue<
127
+ Conf extends keyof DynamicConfigKeys,
128
+ Key extends DynamicConfigKeys[Conf],
129
+ ValType,
130
+ >({
131
+ config,
132
+ key,
133
+ defaultValue,
134
+ customTypeGuard,
135
+ }: {
136
+ config: Conf
137
+ key: Key
138
+ defaultValue: ValType
139
+ customTypeGuard?: (x: unknown) => x is ValType
140
+ }): ValType {
141
+ const dynamicConfig = useDynamicConfig(config)
142
+ const value = dynamicConfig.get(key, defaultValue)
143
+ return checkTypeGuard({ value, defaultValue, customTypeGuard })
144
+ }
145
+
146
+ export function getDynamicConfigValue<
147
+ Conf extends keyof DynamicConfigKeys,
148
+ Key extends DynamicConfigKeys[Conf],
149
+ ValType,
150
+ >({
151
+ config,
152
+ key,
153
+ defaultValue,
154
+ customTypeGuard,
155
+ }: {
156
+ config: Conf
157
+ key: Key
158
+ defaultValue: ValType
159
+ customTypeGuard?: (x: unknown) => x is ValType
160
+ }): ValType {
161
+ const dynamicConfig = getStatsigClient().getDynamicConfig(config)
162
+ const value = dynamicConfig.get(key, defaultValue)
163
+ return checkTypeGuard({ value, defaultValue, customTypeGuard })
164
+ }
165
+
166
+ export function getExperimentValueFromLayer<Layer extends string, Exp extends keyof ExperimentProperties, ValType>({
167
+ layerName,
168
+ param,
169
+ defaultValue,
170
+ customTypeGuard,
171
+ }: {
172
+ layerName: Layer
173
+ param: ExperimentProperties[Exp]
174
+ defaultValue: ValType
175
+ customTypeGuard?: (x: unknown) => x is ValType
176
+ }): ValType {
177
+ const layer = getStatsigClient().getLayer(layerName)
178
+ const value = layer.get(param, defaultValue)
179
+ // we directly get param from layer; these are spread from experiments
180
+ return checkTypeGuard({ value, defaultValue, customTypeGuard })
181
+ }
182
+
183
+ export function useExperimentValueFromLayer<Layer extends string, Exp extends keyof ExperimentProperties, ValType>({
184
+ layerName,
185
+ param,
186
+ defaultValue,
187
+ customTypeGuard,
188
+ }: {
189
+ layerName: Layer
190
+ param: ExperimentProperties[Exp]
191
+ defaultValue: ValType
192
+ customTypeGuard?: (x: unknown) => x is ValType
193
+ }): ValType {
194
+ const layer = useLayer(layerName)
195
+ const value = layer.get(param, defaultValue)
196
+ // we directly get param from layer; these are spread from experiments
197
+ return checkTypeGuard({ value, defaultValue, customTypeGuard })
198
+ }
199
+
200
+ export function checkTypeGuard<ValType>({
201
+ value,
202
+ defaultValue,
203
+ customTypeGuard,
204
+ }: {
205
+ value: TypedReturn<ValType>
206
+ defaultValue: ValType
207
+ customTypeGuard?: (x: unknown) => x is ValType
208
+ }): ValType {
209
+ const isOfDefaultValueType = (val: unknown): val is ValType => typeof val === typeof defaultValue
210
+
211
+ if (customTypeGuard?.(value) || isOfDefaultValueType(value)) {
212
+ return value
213
+ } else {
214
+ return defaultValue
215
+ }
216
+ }
217
+
218
+ export function useStatsigClientStatus(): {
219
+ isStatsigLoading: boolean
220
+ isStatsigReady: boolean
221
+ isStatsigUninitialized: boolean
222
+ } {
223
+ const { client } = useStatsigClient()
224
+ const [statsigStatus, setStatsigStatus] = useState<StatsigLoadingStatus>(client.loadingStatus)
225
+
226
+ useEffect(() => {
227
+ const handler: StatsigClientEventCallback<'values_updated'> = (event) => {
228
+ setStatsigStatus(event.status)
229
+ }
230
+ client.on('values_updated', handler)
231
+ return () => {
232
+ client.off('values_updated', handler)
233
+ }
234
+ }, [client])
235
+
236
+ return useMemo(
237
+ () => ({
238
+ isStatsigLoading: statsigStatus === 'Loading',
239
+ isStatsigReady: statsigStatus === 'Ready',
240
+ isStatsigUninitialized: statsigStatus === 'Uninitialized',
241
+ }),
242
+ [statsigStatus],
243
+ )
244
+ }
package/src/index.ts ADDED
@@ -0,0 +1,97 @@
1
+ export type {
2
+ DatadogIgnoredErrorsValType,
3
+ DatadogSessionSampleRateValType,
4
+ DynamicConfigKeys,
5
+ ForceUpgradeStatus,
6
+ ForceUpgradeTranslations,
7
+ GasStrategies,
8
+ GasStrategyType,
9
+ GasStrategyWithConditions,
10
+ UwULinkAllowlist,
11
+ UwULinkAllowlistItem,
12
+ } from '@luxexchange/gating/src/configs'
13
+ export {
14
+ AllowedV4WethHookAddressesConfigKey,
15
+ BlockedAsyncSubmissionChainIdsConfigKey,
16
+ ChainsConfigKey,
17
+ DatadogIgnoredErrorsConfigKey,
18
+ DatadogSessionSampleRateKey,
19
+ DynamicConfigs,
20
+ EmbeddedWalletConfigKey,
21
+ ExtensionBiometricUnlockConfigKey,
22
+ ExternallyConnectableExtensionConfigKey,
23
+ ForceUpgradeConfigKey,
24
+ HomeScreenExploreTokensConfigKey,
25
+ LPConfigKey,
26
+ NetworkRequestsConfigKey,
27
+ OnDeviceRecoveryConfigKey,
28
+ OutageBannerChainIdConfigKey,
29
+ SwapConfigKey,
30
+ SyncTransactionSubmissionChainIdsConfigKey,
31
+ UwuLinkConfigKey,
32
+ VerifiedAuctionsConfigKey,
33
+ } from '@luxexchange/gating/src/configs'
34
+ export { StatsigCustomAppValue } from '@luxexchange/gating/src/constants'
35
+ export type { ExperimentProperties } from '@luxexchange/gating/src/experiments'
36
+ export {
37
+ EthAsErc20DEXProperties,
38
+ Experiments,
39
+ ExploreBackendSortingProperties,
40
+ LayerProperties,
41
+ Layers,
42
+ NativeTokenPercentageBufferProperties,
43
+ PriceUxUpdateProperties,
44
+ PrivateRpcProperties,
45
+ UnichainFlashblocksProperties,
46
+ } from '@luxexchange/gating/src/experiments'
47
+ export {
48
+ FeatureFlagClient,
49
+ FeatureFlags,
50
+ getFeatureFlagName,
51
+ WALLET_FEATURE_FLAG_NAMES,
52
+ WEB_FEATURE_FLAG_NAMES,
53
+ } from '@luxexchange/gating/src/flags'
54
+ export { getIsHashcashSolverEnabled, useIsHashcashSolverEnabled } from '@luxexchange/gating/src/getIsHashcashSolverEnabled'
55
+ export {
56
+ getIsSessionsPerformanceTrackingEnabled,
57
+ useIsSessionsPerformanceTrackingEnabled,
58
+ } from '@luxexchange/gating/src/getIsPerformanceTrackingEnabled'
59
+ export { getIsSessionServiceEnabled, useIsSessionServiceEnabled } from '@luxexchange/gating/src/getIsSessionServiceEnabled'
60
+ export { getIsSessionUpgradeAutoEnabled } from '@luxexchange/gating/src/getIsSessionUpgradeAutoEnabled'
61
+ export {
62
+ getIsTurnstileSolverEnabled,
63
+ useIsTurnstileSolverEnabled,
64
+ } from '@luxexchange/gating/src/getIsTurnstileSolverEnabled'
65
+ export { getStatsigEnvName } from '@luxexchange/gating/src/getStatsigEnvName'
66
+ export {
67
+ getDynamicConfigValue,
68
+ getExperimentValue,
69
+ getExperimentValueFromLayer,
70
+ getFeatureFlag,
71
+ useDynamicConfigValue,
72
+ useExperimentValue,
73
+ useExperimentValueFromLayer,
74
+ useExperimentValueWithExposureLoggingDisabled,
75
+ useFeatureFlag,
76
+ useFeatureFlagWithExposureLoggingDisabled,
77
+ useFeatureFlagWithLoading,
78
+ useStatsigClientStatus,
79
+ } from '@luxexchange/gating/src/hooks'
80
+ export { LocalOverrideAdapterWrapper } from '@luxexchange/gating/src/LocalOverrideAdapterWrapper'
81
+ export type {
82
+ StatsigOptions,
83
+ StatsigUser,
84
+ StorageProvider,
85
+ } from '@luxexchange/gating/src/sdk/statsig'
86
+ export {
87
+ getOverrideAdapter,
88
+ getStatsigClient,
89
+ StatsigClient,
90
+ StatsigContext,
91
+ StatsigProvider,
92
+ Storage,
93
+ useClientAsyncInit,
94
+ useExperiment,
95
+ useLayer,
96
+ } from '@luxexchange/gating/src/sdk/statsig'
97
+ export { getOverrides } from '@luxexchange/gating/src/utils'
@@ -0,0 +1,33 @@
1
+ import { type StatsigClient } from '@statsig/react-bindings'
2
+ import { StatsigClientRN } from '@statsig/react-native-bindings'
3
+ import { getConfig } from '@luxfi/config'
4
+ import { LocalOverrideAdapterWrapper } from '@luxfi/gating/src/LocalOverrideAdapterWrapper'
5
+
6
+ const config = getConfig()
7
+
8
+ export type { StatsigOptions, StatsigUser, StorageProvider, TypedReturn } from '@statsig/react-native-bindings'
9
+ export {
10
+ StatsigClient,
11
+ StatsigContext,
12
+ StatsigProviderRN as StatsigProvider,
13
+ Storage,
14
+ useClientAsyncInitRN as useClientAsyncInit,
15
+ useDynamicConfig,
16
+ useExperiment,
17
+ useFeatureGate,
18
+ useGateValue,
19
+ useLayer,
20
+ useStatsigClient,
21
+ useStatsigUser,
22
+ } from '@statsig/react-native-bindings'
23
+
24
+ let localOverrideAdapter: LocalOverrideAdapterWrapper | undefined
25
+
26
+ export const getOverrideAdapter = (): LocalOverrideAdapterWrapper => {
27
+ if (!localOverrideAdapter) {
28
+ localOverrideAdapter = new LocalOverrideAdapterWrapper(config.statsigApiKey)
29
+ }
30
+ return localOverrideAdapter
31
+ }
32
+
33
+ export const getStatsigClient = (): StatsigClient => StatsigClientRN.instance(config.statsigApiKey)
@@ -0,0 +1,44 @@
1
+ import { StatsigClient } from '@statsig/react-bindings'
2
+ import { getConfig } from '@luxfi/config'
3
+ import { LocalOverrideAdapterWrapper } from '@luxfi/gating/src/LocalOverrideAdapterWrapper'
4
+ import { isTestEnv } from '@luxfi/utilities/src/environment/env'
5
+
6
+ export {
7
+ StatsigClient,
8
+ StatsigContext,
9
+ type StatsigOptions,
10
+ StatsigProvider,
11
+ type StatsigUser,
12
+ Storage,
13
+ type StorageProvider,
14
+ type TypedReturn,
15
+ useClientAsyncInit,
16
+ useDynamicConfig,
17
+ useExperiment,
18
+ useFeatureGate,
19
+ useGateValue,
20
+ useLayer,
21
+ useStatsigClient,
22
+ useStatsigUser,
23
+ } from '@statsig/react-bindings'
24
+
25
+ let localOverrideAdapter: LocalOverrideAdapterWrapper | undefined
26
+
27
+ function getStatsigApiKeyOrThrow(): string {
28
+ // A dummy key is used in test env b/c the wallet/mobile tests use this file instead of the statsig.native file
29
+ const statsigApiKey = isTestEnv() ? 'dummy-test-key' : getConfig().statsigApiKey
30
+
31
+ // Return empty string instead of throwing for self-hosted deployments without Statsig
32
+ return statsigApiKey || ''
33
+ }
34
+
35
+ export function getOverrideAdapter(): LocalOverrideAdapterWrapper {
36
+ if (!localOverrideAdapter) {
37
+ localOverrideAdapter = new LocalOverrideAdapterWrapper(getStatsigApiKeyOrThrow())
38
+ }
39
+ return localOverrideAdapter
40
+ }
41
+
42
+ export function getStatsigClient(): StatsigClient {
43
+ return StatsigClient.instance(getStatsigApiKeyOrThrow())
44
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { PrecomputedEvaluationsInterface } from '@statsig/js-client'
2
+ import { getOverrideAdapter } from '@luxfi/gating/src/sdk/statsig'
3
+
4
+ export function isStatsigReady(client: PrecomputedEvaluationsInterface): boolean {
5
+ return client.loadingStatus === 'Ready'
6
+ }
7
+
8
+ type GateOverride = [string, boolean]
9
+ type ConfigOverride = [string, Record<string, unknown>]
10
+
11
+ export function getOverrides(client: PrecomputedEvaluationsInterface): {
12
+ configOverrides: ConfigOverride[]
13
+ gateOverrides: GateOverride[]
14
+ } {
15
+ const statsigOverrides = isStatsigReady(client)
16
+ ? getOverrideAdapter().getAllOverrides()
17
+ : { gate: {}, dynamicConfig: {}, layer: {} }
18
+
19
+ const filterNumbers = (value: [string, unknown]): boolean => isNaN(parseInt(value[0], 10))
20
+ const gateOverrides = Object.entries(statsigOverrides.gate).filter(filterNumbers)
21
+ const configOverrides = Object.entries(statsigOverrides.dynamicConfig).filter(filterNumbers)
22
+
23
+ return { configOverrides, gateOverrides }
24
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "../../config/tsconfig/app.json",
3
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json", "src/global.d.ts"],
4
+ "exclude": ["src/**/*.spec.ts", "src/**/*.spec.tsx", "src/**/*.test.ts", "src/**/*.test.tsx"],
5
+ "compilerOptions": {
6
+ "emitDeclarationOnly": true,
7
+ "noEmit": false,
8
+ "types": ["node"],
9
+ "paths": {}
10
+ },
11
+ "references": [
12
+ {
13
+ "path": "../config"
14
+ },
15
+ {
16
+ "path": "../utilities"
17
+ },
18
+ {
19
+ "path": "../api"
20
+ }
21
+ ]
22
+ }
@@ -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
+ }