From bc5f7e8de7151e4f133dd2100c967b57667c4952 Mon Sep 17 00:00:00 2001 From: mulan xia Date: Wed, 26 Jun 2024 15:25:11 -0400 Subject: [PATCH 1/5] wip --- package.json | 2 ++ pnpm-lock.yaml | 25 +++++++++++++++++++++ src/App.tsx | 2 ++ src/hooks/useLocalNotifications.tsx | 3 ++- src/hooks/useStatsig.tsx | 29 +++++++++++++++++++++++++ src/lib/abacus/index.ts | 3 +++ src/pages/token/rewards/RewardsPage.tsx | 6 +++++ 7 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useStatsig.tsx diff --git a/package.json b/package.json index 3b3b2cbbd..35e67e92d 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,8 @@ "@scure/bip32": "^1.3.0", "@scure/bip39": "^1.2.0", "@skip-router/core": "^5.1.0", + "@statsig/js-client": "1.4.0", + "@statsig/react-bindings": "1.4.0", "@tanstack/react-query": "^5.37.1", "@types/lodash": "^4.14.195", "@types/styled-components": "^5.1.26", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2af1059f3..2411b1da4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,6 +143,12 @@ dependencies: '@skip-router/core': specifier: ^5.1.0 version: 5.1.0(@types/react@18.3.3)(chain-registry@1.63.9)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3) + '@statsig/js-client': + specifier: 1.4.0 + version: 1.4.0 + '@statsig/react-bindings': + specifier: 1.4.0 + version: 1.4.0(react@18.2.0) '@tanstack/react-query': specifier: ^5.37.1 version: 5.37.1(react@18.2.0) @@ -6954,6 +6960,25 @@ packages: '@stablelib/wipe': 1.0.1 dev: false + /@statsig/client-core@1.4.0: + resolution: {integrity: sha512-ICz/4DyjMhp9H5GwLTrVUzWqfrO6Bm0RFx1wDd+KGnYx9g6Iwu3FKny5FRybnPSKW47jWidc+FpUi2kQ5AGPqw==} + dev: false + + /@statsig/js-client@1.4.0: + resolution: {integrity: sha512-lDjFdHIvao7Ehj4xLd34WKsaoadsQv84RKv3LsBW/GanMTgquoJHZNQkZz4I1VqtqPkFN8Srqfpo29ATinZ18A==} + dependencies: + '@statsig/client-core': 1.4.0 + dev: false + + /@statsig/react-bindings@1.4.0(react@18.2.0): + resolution: {integrity: sha512-ZRPBF7JOqSyUe34FNghyDtvAcP5Z/Kca4jGTjrzW5lpi86OX0OftOt0LkGW6uDFkjXdpHr/CL+7YB8cSYFU1Eg==} + peerDependencies: + react: ^16.6.3 || ^17.0.0 || ^18.0.0 + dependencies: + '@statsig/client-core': 1.4.0 + react: 18.2.0 + dev: false + /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.23.9): resolution: {integrity: sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==} engines: {node: '>=14'} diff --git a/src/App.tsx b/src/App.tsx index 49e878b87..f5ea82e95 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import { LocaleProvider } from '@/hooks/useLocaleSeparators'; import { NotificationsProvider } from '@/hooks/useNotifications'; import { PotentialMarketsProvider } from '@/hooks/usePotentialMarkets'; import { RestrictionProvider } from '@/hooks/useRestrictions'; +import { StatSigProvider } from '@/hooks/useStatsig'; import { SubaccountProvider } from '@/hooks/useSubaccount'; import '@/styles/constants.css'; @@ -173,6 +174,7 @@ const providers = [ wrapProvider(DialogAreaProvider), wrapProvider(PotentialMarketsProvider), wrapProvider(AppThemeAndColorModeProvider), + wrapProvider(StatSigProvider), ]; const App = () => { diff --git a/src/hooks/useLocalNotifications.tsx b/src/hooks/useLocalNotifications.tsx index 73f27bd45..b783420aa 100644 --- a/src/hooks/useLocalNotifications.tsx +++ b/src/hooks/useLocalNotifications.tsx @@ -13,6 +13,7 @@ import { STATUS_ERROR_GRACE_PERIOD, fetchTransferStatus, trackSkipTx } from '@/l import { useEndpointsConfig } from './useEndpointsConfig'; import { useLocalStorage } from './useLocalStorage'; +import { StatSigFlags, useStatSigGateValue } from './useStatsig'; const LocalNotificationsContext = createContext< ReturnType | undefined @@ -133,7 +134,7 @@ const useLocalNotificationsContext = () => { baseUrl: skip, }; // TODO: replace with statsig call - const useSkip = false; + const useSkip = useStatSigGateValue(StatSigFlags.ffSkipMigration); if (!tracked && useSkip) { const { tx_hash: trackedTxHash } = await trackSkipTx(skipParams); // if no tx hash was returned, transfer has not yet been tracked diff --git a/src/hooks/useStatsig.tsx b/src/hooks/useStatsig.tsx new file mode 100644 index 000000000..4cebdf9df --- /dev/null +++ b/src/hooks/useStatsig.tsx @@ -0,0 +1,29 @@ +import { StatsigClient } from '@statsig/js-client'; +import { StatsigProvider, useStatsigClient } from '@statsig/react-bindings'; + +import { useWalletConnection } from './useWalletConnection'; + +const CLIENT_KEY = 'client-d456XgPoy4wF2fZ9rKRTq6czkLvNHJyNeRRL5cb1RCI'; // TODO move to .env, and preappend with vite? might have to add with vercel + +export enum StatSigFlags { + ffSkipMigration = 'ff_skip_migration', +} + +export const StatSigProvider = ({ children }: { children: React.ReactNode }) => { + const { evmAddress } = useWalletConnection(); + + // TODO is it bad to have in here? + // we have to pass in ip address + const client = new StatsigClient(CLIENT_KEY, { + userID: evmAddress, + }); + + client.initializeSync(); + + return {children} ; +}; + +export const useStatSigGateValue = (gate: StatSigFlags) => { + const { checkGate } = useStatsigClient(); + return checkGate(gate); +}; diff --git a/src/lib/abacus/index.ts b/src/lib/abacus/index.ts index 969183c54..5a5d02bf4 100644 --- a/src/lib/abacus/index.ts +++ b/src/lib/abacus/index.ts @@ -103,6 +103,9 @@ class AbacusStateManager { const appConfigs = AbacusAppConfig.Companion.forWebAppWithIsolatedMargins; appConfigs.onboardingConfigs.squidVersion = OnboardingConfig.SquidVersion.V2; + // appConfigs.onboardingConfigs.routerVendor = OnboardingConfig.RouterVendor.Skip : OnboardingConfig.RouterVendor.Squid; // should be skip, shoul dbe able to just import the client and check the gatee + + // test by console logging here, it shoudl match whatever i expect this.stateManager = new AsyncAbacusStateManager( '', diff --git a/src/pages/token/rewards/RewardsPage.tsx b/src/pages/token/rewards/RewardsPage.tsx index 5c9ed9f55..a625d5206 100644 --- a/src/pages/token/rewards/RewardsPage.tsx +++ b/src/pages/token/rewards/RewardsPage.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react'; +import { useFeatureGate, useStatsigUser } from '@statsig/react-bindings'; import { shallowEqual } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -15,6 +16,7 @@ import { useBreakpoints } from '@/hooks/useBreakpoints'; import { useComplianceState } from '@/hooks/useComplianceState'; import { useEnvConfig } from '@/hooks/useEnvConfig'; import { useEnvFeatures } from '@/hooks/useEnvFeatures'; +import { StatSigFlags, useStatSigGateValue } from '@/hooks/useStatsig'; import { useStringGetter } from '@/hooks/useStringGetter'; import { useTokenConfigs } from '@/hooks/useTokenConfigs'; @@ -55,6 +57,10 @@ const RewardsPage = () => { const { isTablet, isNotTablet } = useBreakpoints(); const canViewAccount = useAppSelector(calculateCanViewAccount); + const { user } = useStatsigUser(); + const gate = useFeatureGate('ff_skip_migration'); + console.log('Xcxc', useStatSigGateValue(StatSigFlags.ffSkipMigration), user, gate); + const { usdcDenom } = useTokenConfigs(); const usdcDecimals = 24; // hardcoded solution; fix in OTE-390 const { isStakingEnabled } = useEnvFeatures(); From e2ae1d2404be2bcd9cb7a66dcfdc43ba998dd91e Mon Sep 17 00:00:00 2001 From: mulan xia Date: Wed, 26 Jun 2024 19:02:38 -0400 Subject: [PATCH 2/5] wip --- src/App.tsx | 2 +- src/hooks/useLocalNotifications.tsx | 5 +++-- src/hooks/useStatsig.tsx | 12 +----------- src/lib/abacus/index.ts | 3 --- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f5ea82e95..f9651527c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -160,6 +160,7 @@ const providers = [ clientId: import.meta.env.VITE_PRIVY_APP_CLIENT_ID, config: privyConfig, }), + wrapProvider(StatSigProvider), wrapProvider(QueryClientProvider, { client: queryClient }), wrapProvider(GrazProvider), wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: configureChainsConfig }), @@ -174,7 +175,6 @@ const providers = [ wrapProvider(DialogAreaProvider), wrapProvider(PotentialMarketsProvider), wrapProvider(AppThemeAndColorModeProvider), - wrapProvider(StatSigProvider), ]; const App = () => { diff --git a/src/hooks/useLocalNotifications.tsx b/src/hooks/useLocalNotifications.tsx index b783420aa..30f01af2d 100644 --- a/src/hooks/useLocalNotifications.tsx +++ b/src/hooks/useLocalNotifications.tsx @@ -32,6 +32,8 @@ const ERROR_COUNT_THRESHOLD = 3; const useLocalNotificationsContext = () => { const { skip } = useEndpointsConfig(); + const useSkip = useStatSigGateValue(StatSigFlags.ffSkipMigration); + const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{ [key: `dydx${string}`]: TransferNotifcation[]; version: string; @@ -133,8 +135,7 @@ const useLocalNotificationsContext = () => { chainId: fromChainId, baseUrl: skip, }; - // TODO: replace with statsig call - const useSkip = useStatSigGateValue(StatSigFlags.ffSkipMigration); + if (!tracked && useSkip) { const { tx_hash: trackedTxHash } = await trackSkipTx(skipParams); // if no tx hash was returned, transfer has not yet been tracked diff --git a/src/hooks/useStatsig.tsx b/src/hooks/useStatsig.tsx index 4cebdf9df..d1fb3a1d0 100644 --- a/src/hooks/useStatsig.tsx +++ b/src/hooks/useStatsig.tsx @@ -1,22 +1,12 @@ import { StatsigClient } from '@statsig/js-client'; import { StatsigProvider, useStatsigClient } from '@statsig/react-bindings'; -import { useWalletConnection } from './useWalletConnection'; - -const CLIENT_KEY = 'client-d456XgPoy4wF2fZ9rKRTq6czkLvNHJyNeRRL5cb1RCI'; // TODO move to .env, and preappend with vite? might have to add with vercel - export enum StatSigFlags { ffSkipMigration = 'ff_skip_migration', } export const StatSigProvider = ({ children }: { children: React.ReactNode }) => { - const { evmAddress } = useWalletConnection(); - - // TODO is it bad to have in here? - // we have to pass in ip address - const client = new StatsigClient(CLIENT_KEY, { - userID: evmAddress, - }); + const client = new StatsigClient(`${import.meta.env.VITE_PRIVY_APP_ID}`, {}); client.initializeSync(); diff --git a/src/lib/abacus/index.ts b/src/lib/abacus/index.ts index 5a5d02bf4..969183c54 100644 --- a/src/lib/abacus/index.ts +++ b/src/lib/abacus/index.ts @@ -103,9 +103,6 @@ class AbacusStateManager { const appConfigs = AbacusAppConfig.Companion.forWebAppWithIsolatedMargins; appConfigs.onboardingConfigs.squidVersion = OnboardingConfig.SquidVersion.V2; - // appConfigs.onboardingConfigs.routerVendor = OnboardingConfig.RouterVendor.Skip : OnboardingConfig.RouterVendor.Squid; // should be skip, shoul dbe able to just import the client and check the gatee - - // test by console logging here, it shoudl match whatever i expect this.stateManager = new AsyncAbacusStateManager( '', From 76c561dfa605e4c8c3dea4ce321dc9dceff34ae5 Mon Sep 17 00:00:00 2001 From: mulan xia Date: Wed, 26 Jun 2024 19:05:55 -0400 Subject: [PATCH 3/5] wip --- src/constants/analytics.ts | 4 ++++ src/hooks/useAnalytics.ts | 7 +++++++ src/hooks/useLocalNotifications.tsx | 1 - src/hooks/useStatsig.tsx | 6 ++++-- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/constants/analytics.ts b/src/constants/analytics.ts index 922dffd35..8518ff80c 100644 --- a/src/constants/analytics.ts +++ b/src/constants/analytics.ts @@ -51,6 +51,9 @@ export const AnalyticsUserProperties = unionize( >(), Version: ofType(), + // StatSigFlags + ffSkipMigration: ofType(), + // Network Network: ofType(), @@ -70,6 +73,7 @@ export const AnalyticsUserPropertyLoggableTypes = { Locale: 'selectedLocale', Breakpoint: 'breakpoint', Version: 'version', + ffSkipMigration: 'ffSkipMigration', Network: 'network', WalletType: 'walletType', WalletConnectionType: 'walletConnectionType', diff --git a/src/hooks/useAnalytics.ts b/src/hooks/useAnalytics.ts index d7894279b..25a702c63 100644 --- a/src/hooks/useAnalytics.ts +++ b/src/hooks/useAnalytics.ts @@ -25,12 +25,14 @@ import { useApiState } from './useApiState'; import { useBreakpoints } from './useBreakpoints'; import { useDydxClient } from './useDydxClient'; import { useSelectedNetwork } from './useSelectedNetwork'; +import { StatSigFlags, useStatSigGateValue } from './useStatsig'; export const useAnalytics = () => { const latestTag = import.meta.env.VITE_LAST_TAG; const { walletType, walletConnectionType, evmAddress, dydxAddress, selectedWalletType } = useAccounts(); const { indexerClient } = useDydxClient(); + const isSkipEnabled = useStatSigGateValue(StatSigFlags.ffSkipMigration); /** User properties */ @@ -67,6 +69,11 @@ export const useAnalytics = () => { } }, [latestTag]); + // AnalyticsUserProperty.ffSkipMigration + useEffect(() => { + identify(AnalyticsUserProperties.ffSkipMigration(isSkipEnabled)); + }, [isSkipEnabled]); + // AnalyticsUserProperty.Network const { selectedNetwork } = useSelectedNetwork(); diff --git a/src/hooks/useLocalNotifications.tsx b/src/hooks/useLocalNotifications.tsx index 30f01af2d..596c25928 100644 --- a/src/hooks/useLocalNotifications.tsx +++ b/src/hooks/useLocalNotifications.tsx @@ -135,7 +135,6 @@ const useLocalNotificationsContext = () => { chainId: fromChainId, baseUrl: skip, }; - if (!tracked && useSkip) { const { tx_hash: trackedTxHash } = await trackSkipTx(skipParams); // if no tx hash was returned, transfer has not yet been tracked diff --git a/src/hooks/useStatsig.tsx b/src/hooks/useStatsig.tsx index d1fb3a1d0..5b33f67b3 100644 --- a/src/hooks/useStatsig.tsx +++ b/src/hooks/useStatsig.tsx @@ -2,12 +2,14 @@ import { StatsigClient } from '@statsig/js-client'; import { StatsigProvider, useStatsigClient } from '@statsig/react-bindings'; export enum StatSigFlags { + // When adding a flag here, make sure to add an analytics tracker in useAnalytics.ts ffSkipMigration = 'ff_skip_migration', } export const StatSigProvider = ({ children }: { children: React.ReactNode }) => { - const client = new StatsigClient(`${import.meta.env.VITE_PRIVY_APP_ID}`, {}); - + const client = new StatsigClient(`${import.meta.env.VITE_STATSIG_CLIENT_KEY}`, { + // TODO: fill in with ip address + }); client.initializeSync(); return {children} ; From 1fd490c240b9ced5123982cedd1f25b5fd7e283b Mon Sep 17 00:00:00 2001 From: mulan xia Date: Wed, 26 Jun 2024 19:08:17 -0400 Subject: [PATCH 4/5] remove testing changes --- src/pages/token/rewards/RewardsPage.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pages/token/rewards/RewardsPage.tsx b/src/pages/token/rewards/RewardsPage.tsx index a625d5206..5c9ed9f55 100644 --- a/src/pages/token/rewards/RewardsPage.tsx +++ b/src/pages/token/rewards/RewardsPage.tsx @@ -1,6 +1,5 @@ import { useEffect } from 'react'; -import { useFeatureGate, useStatsigUser } from '@statsig/react-bindings'; import { shallowEqual } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -16,7 +15,6 @@ import { useBreakpoints } from '@/hooks/useBreakpoints'; import { useComplianceState } from '@/hooks/useComplianceState'; import { useEnvConfig } from '@/hooks/useEnvConfig'; import { useEnvFeatures } from '@/hooks/useEnvFeatures'; -import { StatSigFlags, useStatSigGateValue } from '@/hooks/useStatsig'; import { useStringGetter } from '@/hooks/useStringGetter'; import { useTokenConfigs } from '@/hooks/useTokenConfigs'; @@ -57,10 +55,6 @@ const RewardsPage = () => { const { isTablet, isNotTablet } = useBreakpoints(); const canViewAccount = useAppSelector(calculateCanViewAccount); - const { user } = useStatsigUser(); - const gate = useFeatureGate('ff_skip_migration'); - console.log('Xcxc', useStatSigGateValue(StatSigFlags.ffSkipMigration), user, gate); - const { usdcDenom } = useTokenConfigs(); const usdcDecimals = 24; // hardcoded solution; fix in OTE-390 const { isStakingEnabled } = useEnvFeatures(); From 0fe66b49a5f0828fab16932c50d1b450cdcc1a96 Mon Sep 17 00:00:00 2001 From: Jeremy Lee <37092291+yogurtandjam@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:05:23 -0400 Subject: [PATCH 5/5] feat: statsig integration PoC (#747) --- package.json | 4 +-- pnpm-lock.yaml | 9 +++--- src/App.tsx | 4 +-- src/constants/abacus.ts | 1 + src/constants/analytics.ts | 5 ++-- src/hooks/useAnalytics.ts | 11 ++++---- src/hooks/useInitializePage.ts | 5 +++- src/hooks/useLocalNotifications.tsx | 5 ++-- src/hooks/useStatsig.tsx | 44 ++++++++++++++++++++--------- src/lib/abacus/index.ts | 20 +++++++++++-- src/lib/statsig.ts | 17 +++++++++++ src/types/statsig.ts | 5 ++++ 12 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 src/lib/statsig.ts create mode 100644 src/types/statsig.ts diff --git a/package.json b/package.json index 35e67e92d..74036b8aa 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "lint": "eslint --ext .ts,.tsx src/", "fix-lint": "eslint --fix --ext .ts,.tsx src/", "tag": "node scripts/generate-tag.js", - "test": "vitest run", + "test": "VITE_DISABLE_STATSIG=true vitest run", "tsc": "tsc", "postinstall": "tar -xzC public -f tradingview/tradingview.tgz", "prepare": "husky", @@ -49,7 +49,7 @@ "@cosmjs/proto-signing": "^0.32.1", "@cosmjs/stargate": "^0.32.1", "@cosmjs/tendermint-rpc": "^0.32.1", - "@dydxprotocol/v4-abacus": "1.8.6", + "@dydxprotocol/v4-abacus": "1.8.15", "@dydxprotocol/v4-client-js": "^1.1.24", "@dydxprotocol/v4-localization": "^1.1.140", "@ethersproject/providers": "^5.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2411b1da4..6c37ff3db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ dependencies: specifier: ^0.32.1 version: 0.32.2 '@dydxprotocol/v4-abacus': - specifier: 1.8.6 - version: 1.8.6 + specifier: 1.8.15 + version: 1.8.15 '@dydxprotocol/v4-client-js': specifier: ^1.1.24 version: 1.1.24 @@ -1723,8 +1723,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@dydxprotocol/v4-abacus@1.8.6: - resolution: {integrity: sha512-+uxqUAmJw42ETNaXHjcZE2hfMNaKaRY09oytqxPDkJdXWLbG98n1qyOHxwGOJp6apKuWnYwl4XcKBb3CSX3ejQ==} + /@dydxprotocol/v4-abacus@1.8.15: + resolution: {integrity: sha512-/4xBYtlo4HRY6AtlUybReWDvXBuyflS4N8FqbRWMwxDIY7lgh2alhx0pxHUHWfxFv46F74eOZvzc4O9tVgZjpA==} dependencies: '@js-joda/core': 3.2.0 format-util: 1.0.5 @@ -12979,7 +12979,6 @@ packages: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 dev: false - bundledDependencies: false /event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} diff --git a/src/App.tsx b/src/App.tsx index f9651527c..f4d4fcba8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,7 +19,7 @@ import { LocaleProvider } from '@/hooks/useLocaleSeparators'; import { NotificationsProvider } from '@/hooks/useNotifications'; import { PotentialMarketsProvider } from '@/hooks/usePotentialMarkets'; import { RestrictionProvider } from '@/hooks/useRestrictions'; -import { StatSigProvider } from '@/hooks/useStatsig'; +import { StatsigProvider } from '@/hooks/useStatsig'; import { SubaccountProvider } from '@/hooks/useSubaccount'; import '@/styles/constants.css'; @@ -160,7 +160,7 @@ const providers = [ clientId: import.meta.env.VITE_PRIVY_APP_CLIENT_ID, config: privyConfig, }), - wrapProvider(StatSigProvider), + wrapProvider(StatsigProvider), wrapProvider(QueryClientProvider, { client: queryClient }), wrapProvider(GrazProvider), wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: configureChainsConfig }), diff --git a/src/constants/abacus.ts b/src/constants/abacus.ts index 09a664237..cc10fafe4 100644 --- a/src/constants/abacus.ts +++ b/src/constants/abacus.ts @@ -87,6 +87,7 @@ export type MarketHistoricalFunding = Abacus.exchange.dydx.abacus.output.MarketH export const PerpetualMarketType = Abacus.exchange.dydx.abacus.output.PerpetualMarketType; // ------ Configs ------ // +export const StatsigConfig = Abacus.exchange.dydx.abacus.state.manager.StatsigConfig; export type Configs = Abacus.exchange.dydx.abacus.output.Configs; export type FeeDiscount = Abacus.exchange.dydx.abacus.output.FeeDiscount; export type FeeTier = Abacus.exchange.dydx.abacus.output.FeeTier; diff --git a/src/constants/analytics.ts b/src/constants/analytics.ts index 8518ff80c..a0d948ef4 100644 --- a/src/constants/analytics.ts +++ b/src/constants/analytics.ts @@ -1,3 +1,4 @@ +import { StatSigFlags } from '@/types/statsig'; import { RecordOf, TagsOf, UnionOf, ofType, unionize } from 'unionize'; import type { AbacusApiStatus, HumanReadablePlaceOrderPayload } from './abacus'; @@ -52,7 +53,7 @@ export const AnalyticsUserProperties = unionize( Version: ofType(), // StatSigFlags - ffSkipMigration: ofType(), + StatsigFlags: ofType<{ [key in StatSigFlags]?: boolean }>(), // Network Network: ofType(), @@ -73,7 +74,7 @@ export const AnalyticsUserPropertyLoggableTypes = { Locale: 'selectedLocale', Breakpoint: 'breakpoint', Version: 'version', - ffSkipMigration: 'ffSkipMigration', + StatsigFlags: 'statsigFlags', Network: 'network', WalletType: 'walletType', WalletConnectionType: 'walletConnectionType', diff --git a/src/hooks/useAnalytics.ts b/src/hooks/useAnalytics.ts index 25a702c63..00ba89afe 100644 --- a/src/hooks/useAnalytics.ts +++ b/src/hooks/useAnalytics.ts @@ -25,15 +25,14 @@ import { useApiState } from './useApiState'; import { useBreakpoints } from './useBreakpoints'; import { useDydxClient } from './useDydxClient'; import { useSelectedNetwork } from './useSelectedNetwork'; -import { StatSigFlags, useStatSigGateValue } from './useStatsig'; +import { useAllStatsigGateValues } from './useStatsig'; export const useAnalytics = () => { const latestTag = import.meta.env.VITE_LAST_TAG; const { walletType, walletConnectionType, evmAddress, dydxAddress, selectedWalletType } = useAccounts(); const { indexerClient } = useDydxClient(); - const isSkipEnabled = useStatSigGateValue(StatSigFlags.ffSkipMigration); - + const statsigConfig = useAllStatsigGateValues(); /** User properties */ // AnalyticsUserProperty.Breakpoint @@ -69,10 +68,10 @@ export const useAnalytics = () => { } }, [latestTag]); - // AnalyticsUserProperty.ffSkipMigration + // AnalyticsUserProperty.StatsigConfigs useEffect(() => { - identify(AnalyticsUserProperties.ffSkipMigration(isSkipEnabled)); - }, [isSkipEnabled]); + identify(AnalyticsUserProperties.StatsigFlags(statsigConfig)); + }, [statsigConfig]); // AnalyticsUserProperty.Network const { selectedNetwork } = useSelectedNetwork(); diff --git a/src/hooks/useInitializePage.ts b/src/hooks/useInitializePage.ts index b1e63365e..cdca22a74 100644 --- a/src/hooks/useInitializePage.ts +++ b/src/hooks/useInitializePage.ts @@ -10,6 +10,7 @@ import abacusStateManager from '@/lib/abacus'; import { validateAgainstAvailableEnvironments } from '@/lib/network'; import { useLocalStorage } from './useLocalStorage'; +import { useAllStatsigGateValues } from './useStatsig'; export const useInitializePage = () => { const dispatch = useAppDispatch(); @@ -20,9 +21,11 @@ export const useInitializePage = () => { defaultValue: DEFAULT_APP_ENVIRONMENT, validateFn: validateAgainstAvailableEnvironments, }); + const statsigConfig = useAllStatsigGateValues(); useEffect(() => { dispatch(initializeLocalization()); + abacusStateManager.setStatsigConfigs(statsigConfig); abacusStateManager.start({ network: localStorageNetwork }); - }, []); + }, [dispatch, localStorageNetwork, statsigConfig]); }; diff --git a/src/hooks/useLocalNotifications.tsx b/src/hooks/useLocalNotifications.tsx index 596c25928..de461b34a 100644 --- a/src/hooks/useLocalNotifications.tsx +++ b/src/hooks/useLocalNotifications.tsx @@ -1,5 +1,6 @@ import { createContext, useCallback, useContext, useEffect } from 'react'; +import { StatSigFlags } from '@/types/statsig'; import { useQuery } from '@tanstack/react-query'; import { AnalyticsEvents } from '@/constants/analytics'; @@ -13,7 +14,7 @@ import { STATUS_ERROR_GRACE_PERIOD, fetchTransferStatus, trackSkipTx } from '@/l import { useEndpointsConfig } from './useEndpointsConfig'; import { useLocalStorage } from './useLocalStorage'; -import { StatSigFlags, useStatSigGateValue } from './useStatsig'; +import { useStatsigGateValue } from './useStatsig'; const LocalNotificationsContext = createContext< ReturnType | undefined @@ -32,7 +33,7 @@ const ERROR_COUNT_THRESHOLD = 3; const useLocalNotificationsContext = () => { const { skip } = useEndpointsConfig(); - const useSkip = useStatSigGateValue(StatSigFlags.ffSkipMigration); + const useSkip = useStatsigGateValue(StatSigFlags.ffSkipMigration); const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{ [key: `dydx${string}`]: TransferNotifcation[]; diff --git a/src/hooks/useStatsig.tsx b/src/hooks/useStatsig.tsx index 5b33f67b3..446ea5e54 100644 --- a/src/hooks/useStatsig.tsx +++ b/src/hooks/useStatsig.tsx @@ -1,21 +1,39 @@ -import { StatsigClient } from '@statsig/js-client'; -import { StatsigProvider, useStatsigClient } from '@statsig/react-bindings'; +import { useEffect, useMemo, useState } from 'react'; -export enum StatSigFlags { - // When adding a flag here, make sure to add an analytics tracker in useAnalytics.ts - ffSkipMigration = 'ff_skip_migration', -} +import { StatSigFlags, StatsigConfigType } from '@/types/statsig'; +import { StatsigClient } from '@statsig/js-client'; +import { + StatsigProvider as StatsigProviderInternal, + useStatsigClient, +} from '@statsig/react-bindings'; -export const StatSigProvider = ({ children }: { children: React.ReactNode }) => { - const client = new StatsigClient(`${import.meta.env.VITE_STATSIG_CLIENT_KEY}`, { - // TODO: fill in with ip address - }); - client.initializeSync(); +import { initStatsig } from '@/lib/statsig'; - return {children} ; +export const StatsigProvider = ({ children }: { children: React.ReactNode }) => { + const [client, setClient] = useState(null); + useEffect(() => { + const setAsyncClient = async () => { + const statsigClient = await initStatsig(); + setClient(statsigClient); + }; + setAsyncClient(); + }, [initStatsig]); + // if no client, render without provider until a client exists + if (!client) return
{children}
; + return {children} ; }; -export const useStatSigGateValue = (gate: StatSigFlags) => { +export const useStatsigGateValue = (gate: StatSigFlags) => { const { checkGate } = useStatsigClient(); return checkGate(gate); }; + +export const useAllStatsigGateValues = () => { + const { checkGate } = useStatsigClient(); + const allGateValues = useMemo(() => { + return Object.values(StatSigFlags).reduce((acc, gate) => { + return { ...acc, [gate]: checkGate(gate) }; + }, {} as StatsigConfigType); + }, []); + return allGateValues; +}; diff --git a/src/lib/abacus/index.ts b/src/lib/abacus/index.ts index 969183c54..d8bc237d1 100644 --- a/src/lib/abacus/index.ts +++ b/src/lib/abacus/index.ts @@ -1,3 +1,5 @@ +// eslint-disable-next-line import/no-cycle +import { StatSigFlags } from '@/types/statsig'; import type { LocalWallet, SelectedGasDenom } from '@dydxprotocol/v4-client-js'; import type { @@ -29,6 +31,7 @@ import { HistoricalPnlPeriod, IOImplementations, OnboardingConfig, + StatsigConfig, TradeInputField, TransferInputField, TransferType, @@ -46,7 +49,6 @@ import { getInputTradeOptions, getTransferInputs } from '@/state/inputsSelectors import { LocaleSeparators } from '../numbers'; import AbacusAnalytics from './analytics'; -// eslint-disable-next-line import/no-cycle import AbacusChainTransaction from './dydxChainTransactions'; import AbacusFileSystem from './filesystem'; import AbacusFormatter from './formatter'; @@ -103,7 +105,6 @@ class AbacusStateManager { const appConfigs = AbacusAppConfig.Companion.forWebAppWithIsolatedMargins; appConfigs.onboardingConfigs.squidVersion = OnboardingConfig.SquidVersion.V2; - this.stateManager = new AsyncAbacusStateManager( '', CURRENT_ABACUS_DEPLOYMENT, @@ -409,6 +410,21 @@ class AbacusStateManager { getChainById = (chainId: string) => { return this.stateManager.getChainById(chainId); }; + + /** + * + * Updates Abacus' global StatsigConfig object. + * You must destructure the new flag you want to use from the config and set + * the relevant property on the StatsigConfig object. + * + * TODO: establish standardized naming conventions between + * statsig FF name and boolean propery in abacus StatsigConfig + * https://linear.app/dydx/project/feature-experimentation-6853beb333d7/overview + */ + setStatsigConfigs = (statsigConfig: { [key in StatSigFlags]?: boolean }) => { + const { [StatSigFlags.ffSkipMigration]: useSkip = false } = statsigConfig; + StatsigConfig.useSkip = useSkip; + }; } const abacusStateManager = new AbacusStateManager(); diff --git a/src/lib/statsig.ts b/src/lib/statsig.ts new file mode 100644 index 000000000..719b56095 --- /dev/null +++ b/src/lib/statsig.ts @@ -0,0 +1,17 @@ +import { StatsigClient } from '@statsig/js-client'; + +let statsigClient: StatsigClient; + +export const initStatsig = async () => { + if (statsigClient) return statsigClient; + statsigClient = new StatsigClient( + import.meta.env.VITE_STATSIG_CLIENT_KEY, + {}, + { + disableLogging: import.meta.env.VITE_DISABLE_STATSIG, + disableStorage: import.meta.env.VITE_DISABLE_STATSIG, + } + ); + await statsigClient.initializeAsync(); + return statsigClient; +}; diff --git a/src/types/statsig.ts b/src/types/statsig.ts new file mode 100644 index 000000000..095eae69f --- /dev/null +++ b/src/types/statsig.ts @@ -0,0 +1,5 @@ +export type StatsigConfigType = Record; + +export enum StatSigFlags { + ffSkipMigration = 'ff_skip_migration', +}