diff --git a/apps/base-docs/src/constants.ts b/apps/base-docs/src/constants.ts
new file mode 100644
index 0000000..996aaaa
--- /dev/null
+++ b/apps/base-docs/src/constants.ts
@@ -0,0 +1,9 @@
+import { customFields } from './utils/docusaurusCustomFields';
+
+export const isDevelopment = customFields.nodeEnv === 'development';
+export const amplitudeApiKey = isDevelopment
+ ? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
+ : '2b38c7ac93c0dccc83ebf9acc5107413';
+export const ampDeploymentKey = isDevelopment
+ ? 'client-Wvf63OdaukDZyCBtwgbOvHgGTuASBZFG'
+ : 'client-agFoQg5AOvZ2ZiOChny9RrGk21jG3VrH';
diff --git a/apps/base-docs/src/theme/Root.jsx b/apps/base-docs/src/theme/Root.jsx
index c7363bf..e50089f 100644
--- a/apps/base-docs/src/theme/Root.jsx
+++ b/apps/base-docs/src/theme/Root.jsx
@@ -28,6 +28,7 @@ import { createClient } from 'viem';
import useSprig from 'base-ui/hooks/useSprig';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import ExperimentsProvider from 'base-ui/contexts/Experiments';
coinbaseWallet.preference = 'all';
@@ -204,18 +205,20 @@ export default function Root({ children }) {
`,
}}
/>
-
- {children}
-
-
+
+
+ {children}
+
+
+
diff --git a/apps/base-docs/src/utils/initCCA.ts b/apps/base-docs/src/utils/initCCA.ts
index e597fb8..2673db9 100644
--- a/apps/base-docs/src/utils/initCCA.ts
+++ b/apps/base-docs/src/utils/initCCA.ts
@@ -3,11 +3,10 @@
/* eslint-disable */
// @ts-nocheck
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
-import { customFields } from './docusaurusCustomFields';
import { setCookie, getCookie, deserializeCookie } from './cookieManagement';
import { TrackingPreference } from '@coinbase/cookie-manager';
+import { isDevelopment, amplitudeApiKey } from '../constants';
-const isDevelopment = customFields.nodeEnv === 'development';
// Initialize Client Analytics
const initCCA = () => {
@@ -31,9 +30,7 @@ const initCCA = () => {
init({
isProd: !isDevelopment,
- amplitudeApiKey: isDevelopment
- ? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
- : '2b38c7ac93c0dccc83ebf9acc5107413',
+ amplitudeApiKey: amplitudeApiKey,
platform: PlatformName.web,
projectName: 'base_docs',
showDebugLogging: isDevelopment,
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 75cfe7b..bed6b11 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -89,6 +89,8 @@ const contentSecurityPolicy = {
'https://api.opensea.io', // enables getting ENS avatars
isLocalDevelopment ? 'ws://localhost:3000/' : '',
isLocalDevelopment ? 'http://localhost:3000/' : '',
+ 'https://flag.lab.amplitude.com/sdk/v2/flags',
+ 'https://api.lab.amplitude.com/sdk/v2/vardata',
],
'frame-ancestors': ["'self'", baseXYZDomains],
'form-action': ["'self'", baseXYZDomains],
diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx
index 66a901d..d808bc9 100644
--- a/apps/web/pages/_app.tsx
+++ b/apps/web/pages/_app.tsx
@@ -25,6 +25,7 @@ import { base, baseSepolia, mainnet, sepolia } from 'wagmi/chains';
import ClientAnalyticsScript from '../src/components/ClientAnalyticsScript/ClientAnalyticsScript';
import { Layout } from '../src/components/Layout/Layout';
import { cookieManagerConfig } from '../src/utils/cookieManagerConfig';
+import ExperimentsProvider from 'base-ui/contexts/Experiments';
coinbaseWallet.preference = 'all';
@@ -115,9 +116,11 @@ export default function StaticApp({ Component, pageProps }: AppProps) {
-
-
-
+
+
+
+
+
diff --git a/apps/web/src/constants.ts b/apps/web/src/constants.ts
index 14abff8..2e49a12 100644
--- a/apps/web/src/constants.ts
+++ b/apps/web/src/constants.ts
@@ -7,3 +7,6 @@ export const mainnetLaunchBlogPostURL =
process.env.MAINNET_LAUNCH_BLOG_POST_URL ?? 'https://base.mirror.xyz/';
export const mainnetLaunchFlag = process.env.MAINNET_LAUNCH_FLAG ?? 'false';
export const isDevelopment = nodeEnv === 'development';
+export const ampDeploymentKey = isDevelopment
+ ? 'client-Wvf63OdaukDZyCBtwgbOvHgGTuASBZFG'
+ : 'client-agFoQg5AOvZ2ZiOChny9RrGk21jG3VrH';
diff --git a/apps/web/src/utils/initCCA.ts b/apps/web/src/utils/initCCA.ts
index 4785018..4f45f6f 100644
--- a/apps/web/src/utils/initCCA.ts
+++ b/apps/web/src/utils/initCCA.ts
@@ -16,6 +16,9 @@ const initCCA = (
) => {
let deviceId: string | undefined = deviceIdCookie;
const trackingAllowed: boolean = trackingPreference?.consent.includes('performance');
+ const amplitudeApiKey: string = isDevelopment
+ ? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
+ : '2b38c7ac93c0dccc83ebf9acc5107413';
if (!trackingAllowed) {
deviceId = 'base_web_device_id';
@@ -29,9 +32,7 @@ const initCCA = (
init({
isProd: !isDevelopment,
- amplitudeApiKey: isDevelopment
- ? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
- : '2b38c7ac93c0dccc83ebf9acc5107413',
+ amplitudeApiKey,
platform: PlatformName.web,
projectName: 'base_web',
showDebugLogging: isDevelopment,
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index fa99c62..061a257 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -16,7 +16,7 @@
"next-env.d.ts",
"**/*",
"**/*.json",
- "../../types/**/*"
+ "../../types/**/*",
],
"exclude": [],
"references": [
diff --git a/libs/base-ui/constants.ts b/libs/base-ui/constants.ts
new file mode 100644
index 0000000..2e49a12
--- /dev/null
+++ b/libs/base-ui/constants.ts
@@ -0,0 +1,12 @@
+export const nodeEnv = process.env.NODE_ENV;
+export const docsUrl = process.env.DOCS_URL ?? 'https://docs.base.org';
+export const bridgeUrl = process.env.BRIDGE_URL ?? 'https://bridge.base.org';
+export const greenhouseApiUrl =
+ process.env.GREENHOUSE_HTTPS ?? 'https://boards-api.greenhouse.io/v1';
+export const mainnetLaunchBlogPostURL =
+ process.env.MAINNET_LAUNCH_BLOG_POST_URL ?? 'https://base.mirror.xyz/';
+export const mainnetLaunchFlag = process.env.MAINNET_LAUNCH_FLAG ?? 'false';
+export const isDevelopment = nodeEnv === 'development';
+export const ampDeploymentKey = isDevelopment
+ ? 'client-Wvf63OdaukDZyCBtwgbOvHgGTuASBZFG'
+ : 'client-agFoQg5AOvZ2ZiOChny9RrGk21jG3VrH';
diff --git a/libs/base-ui/contexts/Experiments.tsx b/libs/base-ui/contexts/Experiments.tsx
new file mode 100644
index 0000000..ba51b4a
--- /dev/null
+++ b/libs/base-ui/contexts/Experiments.tsx
@@ -0,0 +1,110 @@
+import React, {
+ createContext,
+ useContext,
+ useEffect,
+ useState,
+ ReactNode,
+ useMemo,
+ useCallback,
+} from 'react';
+import { Experiment, ExperimentClient } from '@amplitude/experiment-js-client';
+
+import { ampDeploymentKey } from '../constants';
+import logEvent, { AnalyticsEventImportance } from '../utils/logEvent';
+
+declare const window: WindowWithAnalytics;
+
+const ExperimentsContext = createContext({
+ experimentClient: null,
+ isReady: false,
+ getUserVariant: () => '',
+});
+
+const experimentClient = Experiment.initialize(ampDeploymentKey, {
+ exposureTrackingProvider: {
+ track: (exposure) => {
+ logEvent('$exposure', exposure, AnalyticsEventImportance.high);
+ },
+ },
+ userProvider: {
+ getUser: () => {
+ return {
+ user_id: window.ClientAnalytics.identity.userId,
+ device_id: window.ClientAnalytics.identity.deviceId,
+ os: window.ClientAnalytics.identity.device_os,
+ language: window.ClientAnalytics.identity.languageCode,
+ country: window.ClientAnalytics.identity.countryCode,
+ };
+ },
+ },
+});
+
+export default function ExperimentsProvider({ children }: ExperimentsProviderProps) {
+ const [isReady, setIsReady] = useState(false);
+
+ useEffect(() => {
+ async function startExperiments() {
+ try {
+ await experimentClient.start();
+ setIsReady(true);
+ } catch (exception) {
+ console.log(`Error starting experiments for ${ampDeploymentKey}:`, exception);
+ }
+ }
+ void startExperiments();
+ }, []);
+
+ const getUserVariant = useCallback(
+ (flagKey: string): string => {
+ if (!isReady) {
+ return '';
+ }
+ if (!experimentClient) {
+ console.error('No experiment clients found');
+ return '';
+ }
+ const variant = experimentClient.variant(flagKey);
+ return variant.value ?? '';
+ },
+ [isReady],
+ );
+
+ const values = useMemo(() => {
+ return { experimentClient, isReady, getUserVariant };
+ }, [isReady, getUserVariant]);
+
+ return {children};
+}
+
+const useExperiments = () => {
+ const context = useContext(ExperimentsContext);
+ if (context === undefined) {
+ throw new Error('useExperiments must be used within an ExperimentsProvider');
+ }
+ return context;
+};
+
+export { useExperiments };
+
+type WindowWithAnalytics = Window &
+ typeof globalThis & {
+ ClientAnalytics: {
+ identity: {
+ userId: string;
+ deviceId: string;
+ device_os: string;
+ languageCode: string;
+ countryCode: string;
+ };
+ };
+ };
+
+type ExperimentsContextProps = {
+ experimentClient: ExperimentClient | null;
+ isReady: boolean;
+ getUserVariant: (flagKey: string) => string;
+};
+
+type ExperimentsProviderProps = {
+ children: ReactNode;
+};
diff --git a/libs/base-ui/package.json b/libs/base-ui/package.json
index 4d3fcc4..0490a69 100644
--- a/libs/base-ui/package.json
+++ b/libs/base-ui/package.json
@@ -6,6 +6,7 @@
"types": "./index.ts",
"main": "./index.ts",
"dependencies": {
+ "@amplitude/experiment-js-client": "^1.11.0",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-select": "^1.2.1",
"@sprig-technologies/sprig-browser": "^2.29.0",
diff --git a/libs/base-ui/utils/logEvent.ts b/libs/base-ui/utils/logEvent.ts
index f827eb7..d147635 100644
--- a/libs/base-ui/utils/logEvent.ts
+++ b/libs/base-ui/utils/logEvent.ts
@@ -65,6 +65,9 @@ type CCAEventData = {
context?: string;
userId?: string;
wallet_type?: string;
+ flag_key?: string;
+ variant?: string | undefined;
+ experiment_key?: string | undefined;
};
type AnalyticsEventData = {
diff --git a/yarn.lock b/yarn.lock
index ac36611..eb818be 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -215,6 +215,42 @@ __metadata:
languageName: node
linkType: hard
+"@amplitude/analytics-connector@npm:^1.5.0":
+ version: 1.5.0
+ resolution: "@amplitude/analytics-connector@npm:1.5.0"
+ checksum: 157115b642e8b254a88184fe3294150321fdd482388f262bdc08f9ab0c68b01b5f10d2e35a924edb8a8380f0944f8b1363133c2a3f42b59e33da319bfe21c8fa
+ languageName: node
+ linkType: hard
+
+"@amplitude/experiment-core@npm:^0.8.0":
+ version: 0.8.0
+ resolution: "@amplitude/experiment-core@npm:0.8.0"
+ dependencies:
+ js-base64: ^3.7.5
+ checksum: 2e2c28f187b4f8072fe30589af95378c220917e949ae8a4c444dd8a6fdbe8bd9c8d0d105808b37e5e5fd59f16cbfa486c66f60847c211a0e424c6e7deb1693aa
+ languageName: node
+ linkType: hard
+
+"@amplitude/experiment-js-client@npm:^1.11.0":
+ version: 1.11.0
+ resolution: "@amplitude/experiment-js-client@npm:1.11.0"
+ dependencies:
+ "@amplitude/analytics-connector": ^1.5.0
+ "@amplitude/experiment-core": ^0.8.0
+ "@amplitude/ua-parser-js": ^0.7.31
+ base64-js: 1.5.1
+ unfetch: 4.1.0
+ checksum: bc7ee5a521248ac83903b079fa61c7a884a3e27bd3573e4cedbb9f455ebe7af1e6fdc16a3e62988bf807ab9d74319de729fde78c42d4b0825f7c1a52d7753ff1
+ languageName: node
+ linkType: hard
+
+"@amplitude/ua-parser-js@npm:^0.7.31":
+ version: 0.7.33
+ resolution: "@amplitude/ua-parser-js@npm:0.7.33"
+ checksum: b08ce4cd4e96fed9eebffadb4060d24751cea80caf4265e6b829b7cfdb6561431386015674b3ac330a8987b990e2ca70725d4298eb1438beccb85264848b0b34
+ languageName: node
+ linkType: hard
+
"@ampproject/remapping@npm:^2.2.0":
version: 2.2.1
resolution: "@ampproject/remapping@npm:2.2.1"
@@ -9563,6 +9599,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "base-ui@workspace:libs/base-ui"
dependencies:
+ "@amplitude/experiment-js-client": ^1.11.0
"@radix-ui/react-icons": ^1.3.0
"@radix-ui/react-select": ^1.2.1
"@sprig-technologies/sprig-browser": ^2.29.0
@@ -9582,7 +9619,7 @@ __metadata:
languageName: node
linkType: hard
-"base64-js@npm:^1.3.1":
+"base64-js@npm:1.5.1, base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005
@@ -16235,6 +16272,13 @@ __metadata:
languageName: node
linkType: hard
+"js-base64@npm:^3.7.5":
+ version: 3.7.7
+ resolution: "js-base64@npm:3.7.7"
+ checksum: d1b02971db9dc0fd35baecfaf6ba499731fb44fe3373e7e1d6681fbd3ba665f29e8d9d17910254ef8104e2cb8b44117fe4202d3dc54c7cafe9ba300fe5433358
+ languageName: node
+ linkType: hard
+
"js-cookie@npm:^3.0.5":
version: 3.0.5
resolution: "js-cookie@npm:3.0.5"
@@ -23082,6 +23126,13 @@ __metadata:
languageName: node
linkType: hard
+"unfetch@npm:4.1.0":
+ version: 4.1.0
+ resolution: "unfetch@npm:4.1.0"
+ checksum: 8a0fee1e0f6ad8b3a2966fa199d07716affc3682d8e1c2c0cc138e5e5d2a2e0627d8c3321a4529a79e8a58332955bf80ac6c018f1dcc6de652026a7c3257d726
+ languageName: node
+ linkType: hard
+
"unfetch@npm:^4.2.0":
version: 4.2.0
resolution: "unfetch@npm:4.2.0"