@b3dotfun/sdk 0.0.30-alpha.6 → 0.0.30-alpha.8

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.
Files changed (29) hide show
  1. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +5 -0
  2. package/dist/cjs/global-account/react/components/ManageAccount/ContentTokens.js +21 -0
  3. package/dist/cjs/global-account/react/hooks/index.d.ts +2 -1
  4. package/dist/cjs/global-account/react/hooks/index.js +5 -3
  5. package/dist/cjs/global-account/react/hooks/useAnalytics.d.ts +7 -0
  6. package/dist/cjs/global-account/react/hooks/useAnalytics.js +29 -0
  7. package/dist/cjs/global-account/react/hooks/useSimBalance.js +5 -1
  8. package/dist/cjs/global-account/utils/analytics.d.ts +10 -0
  9. package/dist/cjs/global-account/utils/analytics.js +54 -0
  10. package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +5 -0
  11. package/dist/esm/global-account/react/components/ManageAccount/ContentTokens.js +23 -2
  12. package/dist/esm/global-account/react/hooks/index.d.ts +2 -1
  13. package/dist/esm/global-account/react/hooks/index.js +2 -1
  14. package/dist/esm/global-account/react/hooks/useAnalytics.d.ts +7 -0
  15. package/dist/esm/global-account/react/hooks/useAnalytics.js +26 -0
  16. package/dist/esm/global-account/react/hooks/useSimBalance.js +5 -1
  17. package/dist/esm/global-account/utils/analytics.d.ts +10 -0
  18. package/dist/esm/global-account/utils/analytics.js +49 -0
  19. package/dist/types/global-account/react/hooks/index.d.ts +2 -1
  20. package/dist/types/global-account/react/hooks/useAnalytics.d.ts +7 -0
  21. package/dist/types/global-account/utils/analytics.d.ts +10 -0
  22. package/package.json +1 -1
  23. package/src/global-account/react/components/B3Provider/B3Provider.tsx +6 -0
  24. package/src/global-account/react/components/ManageAccount/ContentTokens.tsx +27 -1
  25. package/src/global-account/react/hooks/index.ts +2 -1
  26. package/src/global-account/react/hooks/useAnalytics.tsx +30 -0
  27. package/src/global-account/react/hooks/useSimBalance.ts +6 -3
  28. package/src/global-account/utils/analytics.ts +54 -0
  29. package/src/{anyspend/types → types}/window.d.ts +5 -1
@@ -5,6 +5,7 @@ exports.B3Provider = B3Provider;
5
5
  exports.InnerProvider = InnerProvider;
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("../../../../global-account/react");
8
+ const analytics_1 = require("../../../../global-account/utils/analytics");
8
9
  const supported_1 = require("../../../../shared/constants/chains/supported");
9
10
  const react_query_1 = require("@tanstack/react-query");
10
11
  const react_2 = require("react");
@@ -33,6 +34,10 @@ const queryClient = new react_query_1.QueryClient();
33
34
  * Main B3Provider component
34
35
  */
35
36
  function B3Provider({ theme = "light", children, accountOverride, environment, automaticallySetFirstEoa, simDuneApiKey, toaster, }) {
37
+ // Initialize Google Analytics on mount
38
+ (0, react_2.useEffect)(() => {
39
+ (0, analytics_1.loadGA4Script)();
40
+ }, []);
36
41
  return ((0, jsx_runtime_1.jsx)(wagmi_1.WagmiProvider, { config: exports.wagmiConfig, children: (0, jsx_runtime_1.jsx)(react_query_1.QueryClientProvider, { client: queryClient, children: (0, jsx_runtime_1.jsx)(react_3.ThirdwebProvider, { children: (0, jsx_runtime_1.jsx)(react_1.TooltipProvider, { children: (0, jsx_runtime_1.jsx)(InnerProvider, { accountOverride: accountOverride, environment: environment, theme: theme, automaticallySetFirstEoa: !!automaticallySetFirstEoa, children: (0, jsx_runtime_1.jsxs)(react_1.RelayKitProviderWrapper, { simDuneApiKey: simDuneApiKey, children: [children, (0, jsx_runtime_1.jsx)(StyleRoot_1.StyleRoot, { id: "b3-root" }), (0, jsx_runtime_1.jsx)(sonner_1.Toaster, { theme: theme, position: toaster?.position, style: toaster?.style })] }) }) }) }) }) }));
37
42
  }
38
43
  /**
@@ -47,6 +47,8 @@ function ContentTokens({ activeTab }) {
47
47
  const { data: simBalance, refetch: refetchSimBalance, isLoading: isLoadingBalance } = (0, react_1.useSimBalance)(account?.address);
48
48
  // === BLOCKCHAIN INTERACTION ===
49
49
  const { switchChainAndExecute } = (0, react_1.useUnifiedChainSwitchAndExecute)();
50
+ // === ANALYTICS ===
51
+ const { sendAnalyticsEvent } = (0, react_1.useAnalytics)();
50
52
  // === ADDRESS VALIDATION ===
51
53
  // Handle recipient address change with real-time validation using viem
52
54
  const handleRecipientAddressChange = (value) => {
@@ -133,6 +135,13 @@ function ContentTokens({ activeTab }) {
133
135
  }
134
136
  setIsSending(true);
135
137
  const amountInWei = (0, viem_1.parseUnits)(sendAmount, displayToken.decimals);
138
+ // Prepare analytics event data
139
+ const analyticsData = {
140
+ amount: sendAmount,
141
+ symbol: displayToken.symbol,
142
+ chain_id: displayToken.chain_id,
143
+ address: displayToken.address,
144
+ };
136
145
  try {
137
146
  const sendTokenData = (0, viem_1.encodeFunctionData)({
138
147
  abi: viem_1.erc20Abi,
@@ -145,11 +154,23 @@ function ContentTokens({ activeTab }) {
145
154
  value: displayToken.address === "native" ? amountInWei : BigInt(0),
146
155
  });
147
156
  if (tx) {
157
+ // Track successful send
158
+ sendAnalyticsEvent("send_token_button_click", {
159
+ ...analyticsData,
160
+ success: true,
161
+ tx: (0, anyspend_1.getExplorerTxUrl)(displayToken.chain_id, tx),
162
+ });
148
163
  // Reset form
149
164
  setSendAmount("");
150
165
  }
151
166
  }
152
167
  catch (error) {
168
+ // Track failed send
169
+ sendAnalyticsEvent("send_token_button_click", {
170
+ ...analyticsData,
171
+ success: false,
172
+ reason: error.message || "Unknown error",
173
+ });
153
174
  // Error
154
175
  sonner_1.toast.error(`Failed to send ${displayToken.symbol}: ${error.message || "Unknown error"}`);
155
176
  }
@@ -1,6 +1,7 @@
1
1
  export { useAccountAssets } from "./useAccountAssets";
2
2
  export { useAccountWallet } from "./useAccountWallet";
3
3
  export { useAddTWSessionKey } from "./useAddTWSessionKey";
4
+ export { useAnalytics } from "./useAnalytics";
4
5
  export { useAuthentication } from "./useAuthentication";
5
6
  export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
6
7
  export { useB3EnsName } from "./useB3EnsName";
@@ -18,13 +19,13 @@ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
18
19
  export { useMediaQuery } from "./useMediaQuery";
19
20
  export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance";
20
21
  export { useOneBalance } from "./useOneBalance";
21
- export { useSimBalance } from "./useSimBalance";
22
22
  export { useProfile, useProfilePreference, type CombinedProfile, type PreferenceRequestBody, type Profile, } from "./useProfile";
23
23
  export { useQueryB3 } from "./useQueryB3";
24
24
  export { useQueryBSMNT } from "./useQueryBSMNT";
25
25
  export { useRemoveSessionKey } from "./useRemoveSessionKey";
26
26
  export { useRouter } from "./useRouter";
27
27
  export { useSearchParamsSSR } from "./useSearchParamsSSR";
28
+ export { useSimBalance } from "./useSimBalance";
28
29
  export { useSiwe } from "./useSiwe";
29
30
  export { useTokenBalance } from "./useTokenBalance";
30
31
  export { useTokenBalancesByChain } from "./useTokenBalancesByChain";
@@ -14,13 +14,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.useURLParams = exports.useUnifiedChainSwitchAndExecute = exports.useTokensFromAddress = exports.useTokenPriceWithFallback = exports.useTokenPrice = exports.useTokenFromUrl = exports.useTokenData = exports.useTokenBalancesByChain = exports.useTokenBalance = exports.useSiwe = exports.useSearchParamsSSR = exports.useRouter = exports.useRemoveSessionKey = exports.useQueryBSMNT = exports.useQueryB3 = exports.useProfilePreference = exports.useProfile = exports.useSimBalance = exports.useOneBalance = exports.useNativeBalanceFromRPC = exports.useNativeBalance = exports.useMediaQuery = exports.useIsomorphicLayoutEffect = exports.useIsMobile = exports.useHasMounted = exports.useHandleConnectWithPrivy = exports.useGetGeo = exports.useGetAllTWSigners = exports.useExchangeRate = exports.useConnect = exports.useChainSwitchWithAction = exports.useBestTransactionPath = exports.useB3EnsName = exports.useB3BalanceFromAddresses = exports.useAuthentication = exports.useAddTWSessionKey = exports.useAccountWallet = exports.useAccountAssets = void 0;
17
+ exports.useURLParams = exports.useUnifiedChainSwitchAndExecute = exports.useTokensFromAddress = exports.useTokenPriceWithFallback = exports.useTokenPrice = exports.useTokenFromUrl = exports.useTokenData = exports.useTokenBalancesByChain = exports.useTokenBalance = exports.useSiwe = exports.useSimBalance = exports.useSearchParamsSSR = exports.useRouter = exports.useRemoveSessionKey = exports.useQueryBSMNT = exports.useQueryB3 = exports.useProfilePreference = exports.useProfile = exports.useOneBalance = exports.useNativeBalanceFromRPC = exports.useNativeBalance = exports.useMediaQuery = exports.useIsomorphicLayoutEffect = exports.useIsMobile = exports.useHasMounted = exports.useHandleConnectWithPrivy = exports.useGetGeo = exports.useGetAllTWSigners = exports.useExchangeRate = exports.useConnect = exports.useChainSwitchWithAction = exports.useBestTransactionPath = exports.useB3EnsName = exports.useB3BalanceFromAddresses = exports.useAuthentication = exports.useAnalytics = exports.useAddTWSessionKey = exports.useAccountWallet = exports.useAccountAssets = void 0;
18
18
  var useAccountAssets_1 = require("./useAccountAssets");
19
19
  Object.defineProperty(exports, "useAccountAssets", { enumerable: true, get: function () { return useAccountAssets_1.useAccountAssets; } });
20
20
  var useAccountWallet_1 = require("./useAccountWallet");
21
21
  Object.defineProperty(exports, "useAccountWallet", { enumerable: true, get: function () { return useAccountWallet_1.useAccountWallet; } });
22
22
  var useAddTWSessionKey_1 = require("./useAddTWSessionKey");
23
23
  Object.defineProperty(exports, "useAddTWSessionKey", { enumerable: true, get: function () { return useAddTWSessionKey_1.useAddTWSessionKey; } });
24
+ var useAnalytics_1 = require("./useAnalytics");
25
+ Object.defineProperty(exports, "useAnalytics", { enumerable: true, get: function () { return useAnalytics_1.useAnalytics; } });
24
26
  var useAuthentication_1 = require("./useAuthentication");
25
27
  Object.defineProperty(exports, "useAuthentication", { enumerable: true, get: function () { return useAuthentication_1.useAuthentication; } });
26
28
  var useB3BalanceFromAddresses_1 = require("./useB3BalanceFromAddresses");
@@ -55,8 +57,6 @@ Object.defineProperty(exports, "useNativeBalance", { enumerable: true, get: func
55
57
  Object.defineProperty(exports, "useNativeBalanceFromRPC", { enumerable: true, get: function () { return useNativeBalance_1.useNativeBalanceFromRPC; } });
56
58
  var useOneBalance_1 = require("./useOneBalance");
57
59
  Object.defineProperty(exports, "useOneBalance", { enumerable: true, get: function () { return useOneBalance_1.useOneBalance; } });
58
- var useSimBalance_1 = require("./useSimBalance");
59
- Object.defineProperty(exports, "useSimBalance", { enumerable: true, get: function () { return useSimBalance_1.useSimBalance; } });
60
60
  var useProfile_1 = require("./useProfile");
61
61
  Object.defineProperty(exports, "useProfile", { enumerable: true, get: function () { return useProfile_1.useProfile; } });
62
62
  Object.defineProperty(exports, "useProfilePreference", { enumerable: true, get: function () { return useProfile_1.useProfilePreference; } });
@@ -70,6 +70,8 @@ var useRouter_1 = require("./useRouter");
70
70
  Object.defineProperty(exports, "useRouter", { enumerable: true, get: function () { return useRouter_1.useRouter; } });
71
71
  var useSearchParamsSSR_1 = require("./useSearchParamsSSR");
72
72
  Object.defineProperty(exports, "useSearchParamsSSR", { enumerable: true, get: function () { return useSearchParamsSSR_1.useSearchParamsSSR; } });
73
+ var useSimBalance_1 = require("./useSimBalance");
74
+ Object.defineProperty(exports, "useSimBalance", { enumerable: true, get: function () { return useSimBalance_1.useSimBalance; } });
73
75
  var useSiwe_1 = require("./useSiwe");
74
76
  Object.defineProperty(exports, "useSiwe", { enumerable: true, get: function () { return useSiwe_1.useSiwe; } });
75
77
  var useTokenBalance_1 = require("./useTokenBalance");
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Analytics hook that provides sendAnalyticsEvent function
3
+ * Automatically includes user address from useAccountWallet
4
+ */
5
+ export declare function useAnalytics(): {
6
+ sendAnalyticsEvent: (eventName: string, parameters?: Record<string, any>) => void;
7
+ };
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAnalytics = useAnalytics;
4
+ const analytics_1 = require("../../../global-account/utils/analytics");
5
+ const useAccountWallet_1 = require("./useAccountWallet");
6
+ /**
7
+ * Analytics hook that provides sendAnalyticsEvent function
8
+ * Automatically includes user address from useAccountWallet
9
+ */
10
+ function useAnalytics() {
11
+ const { address } = (0, useAccountWallet_1.useAccountWallet)();
12
+ /**
13
+ * Sends an analytics event to Google Analytics 4
14
+ * @param eventName - The name of the event to track
15
+ * @param parameters - Additional parameters to include with the event
16
+ */
17
+ const sendAnalyticsEvent = (eventName, parameters) => {
18
+ // Merge user address with custom parameters
19
+ const eventData = {
20
+ user_address: address,
21
+ ...parameters,
22
+ };
23
+ // Send event to GA4 using utility function
24
+ (0, analytics_1.sendGA4Event)(eventName, eventData);
25
+ };
26
+ return {
27
+ sendAnalyticsEvent,
28
+ };
29
+ }
@@ -5,7 +5,11 @@ const react_query_1 = require("@tanstack/react-query");
5
5
  async function fetchSimBalance(address) {
6
6
  if (!address)
7
7
  throw new Error("Address is required");
8
- const response = await fetch(`https://simdune-api.sean-430.workers.dev/?url=https://api.sim.dune.com/v1/evm/balances/${address}?metadata=logo&localkey=${process.env.PUBLIC_LOCAL_KEY}`);
8
+ let url = `https://simdune-api.sean-430.workers.dev/?url=https://api.sim.dune.com/v1/evm/balances/${address}?metadata=logo&chain_ids=mainnet`;
9
+ if (process.env.PUBLIC_LOCAL_KEY) {
10
+ url += `&localkey=${process.env.PUBLIC_LOCAL_KEY}`;
11
+ }
12
+ const response = await fetch(url);
9
13
  if (!response.ok) {
10
14
  throw new Error(`Failed to fetch balance: ${response.statusText}`);
11
15
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Load Google Analytics 4 script and initialize
3
+ */
4
+ export declare const loadGA4Script: () => void;
5
+ /**
6
+ * Send an analytics event to Google Analytics 4
7
+ * @param eventName - The name of the event to track
8
+ * @param parameters - Additional parameters to include with the event
9
+ */
10
+ export declare const sendGA4Event: (eventName: string, parameters?: Record<string, any>) => void;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendGA4Event = exports.loadGA4Script = void 0;
4
+ const GA4_MEASUREMENT_ID = "G-Z67JCLMNZE";
5
+ /**
6
+ * Initialize Google Analytics 4
7
+ */
8
+ const initializeGA4 = () => {
9
+ // Only initialize in browser environment
10
+ if (typeof window === "undefined")
11
+ return;
12
+ // Create gtag function if it doesn't exist
13
+ if (!window.gtag) {
14
+ window.dataLayer = window.dataLayer || [];
15
+ window.gtag = function gtag() {
16
+ window.dataLayer.push(arguments);
17
+ };
18
+ }
19
+ // Configure GA4
20
+ window.gtag("js", new Date());
21
+ window.gtag("config", GA4_MEASUREMENT_ID, {
22
+ page_location: window.location.href,
23
+ page_hostname: window.location.hostname,
24
+ });
25
+ };
26
+ /**
27
+ * Load Google Analytics 4 script and initialize
28
+ */
29
+ const loadGA4Script = () => {
30
+ if (typeof window === "undefined")
31
+ return;
32
+ // Check if script is already loaded
33
+ if (document.querySelector(`script[src*="${GA4_MEASUREMENT_ID}"]`))
34
+ return;
35
+ const script = document.createElement("script");
36
+ script.async = true;
37
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${GA4_MEASUREMENT_ID}`;
38
+ document.head.appendChild(script);
39
+ script.onload = initializeGA4;
40
+ };
41
+ exports.loadGA4Script = loadGA4Script;
42
+ /**
43
+ * Send an analytics event to Google Analytics 4
44
+ * @param eventName - The name of the event to track
45
+ * @param parameters - Additional parameters to include with the event
46
+ */
47
+ const sendGA4Event = (eventName, parameters) => {
48
+ // Only send events in browser environment
49
+ if (typeof window === "undefined" || !window.gtag)
50
+ return;
51
+ // Send event to GA4
52
+ window.gtag("event", eventName, parameters || {});
53
+ };
54
+ exports.sendGA4Event = sendGA4Event;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { RelayKitProviderWrapper, TooltipProvider, useAuthStore } from "../../../../global-account/react/index.js";
3
+ import { loadGA4Script } from "../../../../global-account/utils/analytics.js";
3
4
  import { supportedChains } from "../../../../shared/constants/chains/supported.js";
4
5
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5
6
  import { useCallback, useEffect, useState } from "react";
@@ -28,6 +29,10 @@ const queryClient = new QueryClient();
28
29
  * Main B3Provider component
29
30
  */
30
31
  export function B3Provider({ theme = "light", children, accountOverride, environment, automaticallySetFirstEoa, simDuneApiKey, toaster, }) {
32
+ // Initialize Google Analytics on mount
33
+ useEffect(() => {
34
+ loadGA4Script();
35
+ }, []);
31
36
  return (_jsx(WagmiProvider, { config: wagmiConfig, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(ThirdwebProvider, { children: _jsx(TooltipProvider, { children: _jsx(InnerProvider, { accountOverride: accountOverride, environment: environment, theme: theme, automaticallySetFirstEoa: !!automaticallySetFirstEoa, children: _jsxs(RelayKitProviderWrapper, { simDuneApiKey: simDuneApiKey, children: [children, _jsx(StyleRoot, { id: "b3-root" }), _jsx(Toaster, { theme: theme, position: toaster?.position, style: toaster?.style })] }) }) }) }) }) }));
32
37
  }
33
38
  /**
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { ALL_CHAINS } from "../../../../anyspend/index.js";
2
+ import { ALL_CHAINS, getExplorerTxUrl } from "../../../../anyspend/index.js";
3
3
  import { ChainTokenIcon } from "../../../../anyspend/react/components/common/ChainTokenIcon.js";
4
- import { Button, TransitionPanel, useSimBalance, useUnifiedChainSwitchAndExecute, } from "../../../../global-account/react/index.js";
4
+ import { Button, TransitionPanel, useAnalytics, useSimBalance, useUnifiedChainSwitchAndExecute, } from "../../../../global-account/react/index.js";
5
5
  import { formatDisplayNumber, formatTokenAmount } from "../../../../shared/utils/number.js";
6
6
  import { ArrowLeft, CircleHelp, Copy, Loader2, Send } from "lucide-react";
7
7
  import { useEffect, useMemo, useRef, useState } from "react";
@@ -44,6 +44,8 @@ export function ContentTokens({ activeTab }) {
44
44
  const { data: simBalance, refetch: refetchSimBalance, isLoading: isLoadingBalance } = useSimBalance(account?.address);
45
45
  // === BLOCKCHAIN INTERACTION ===
46
46
  const { switchChainAndExecute } = useUnifiedChainSwitchAndExecute();
47
+ // === ANALYTICS ===
48
+ const { sendAnalyticsEvent } = useAnalytics();
47
49
  // === ADDRESS VALIDATION ===
48
50
  // Handle recipient address change with real-time validation using viem
49
51
  const handleRecipientAddressChange = (value) => {
@@ -130,6 +132,13 @@ export function ContentTokens({ activeTab }) {
130
132
  }
131
133
  setIsSending(true);
132
134
  const amountInWei = parseUnits(sendAmount, displayToken.decimals);
135
+ // Prepare analytics event data
136
+ const analyticsData = {
137
+ amount: sendAmount,
138
+ symbol: displayToken.symbol,
139
+ chain_id: displayToken.chain_id,
140
+ address: displayToken.address,
141
+ };
133
142
  try {
134
143
  const sendTokenData = encodeFunctionData({
135
144
  abi: erc20Abi,
@@ -142,11 +151,23 @@ export function ContentTokens({ activeTab }) {
142
151
  value: displayToken.address === "native" ? amountInWei : BigInt(0),
143
152
  });
144
153
  if (tx) {
154
+ // Track successful send
155
+ sendAnalyticsEvent("send_token_button_click", {
156
+ ...analyticsData,
157
+ success: true,
158
+ tx: getExplorerTxUrl(displayToken.chain_id, tx),
159
+ });
145
160
  // Reset form
146
161
  setSendAmount("");
147
162
  }
148
163
  }
149
164
  catch (error) {
165
+ // Track failed send
166
+ sendAnalyticsEvent("send_token_button_click", {
167
+ ...analyticsData,
168
+ success: false,
169
+ reason: error.message || "Unknown error",
170
+ });
150
171
  // Error
151
172
  toast.error(`Failed to send ${displayToken.symbol}: ${error.message || "Unknown error"}`);
152
173
  }
@@ -1,6 +1,7 @@
1
1
  export { useAccountAssets } from "./useAccountAssets";
2
2
  export { useAccountWallet } from "./useAccountWallet";
3
3
  export { useAddTWSessionKey } from "./useAddTWSessionKey";
4
+ export { useAnalytics } from "./useAnalytics";
4
5
  export { useAuthentication } from "./useAuthentication";
5
6
  export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
6
7
  export { useB3EnsName } from "./useB3EnsName";
@@ -18,13 +19,13 @@ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
18
19
  export { useMediaQuery } from "./useMediaQuery";
19
20
  export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance";
20
21
  export { useOneBalance } from "./useOneBalance";
21
- export { useSimBalance } from "./useSimBalance";
22
22
  export { useProfile, useProfilePreference, type CombinedProfile, type PreferenceRequestBody, type Profile, } from "./useProfile";
23
23
  export { useQueryB3 } from "./useQueryB3";
24
24
  export { useQueryBSMNT } from "./useQueryBSMNT";
25
25
  export { useRemoveSessionKey } from "./useRemoveSessionKey";
26
26
  export { useRouter } from "./useRouter";
27
27
  export { useSearchParamsSSR } from "./useSearchParamsSSR";
28
+ export { useSimBalance } from "./useSimBalance";
28
29
  export { useSiwe } from "./useSiwe";
29
30
  export { useTokenBalance } from "./useTokenBalance";
30
31
  export { useTokenBalancesByChain } from "./useTokenBalancesByChain";
@@ -1,6 +1,7 @@
1
1
  export { useAccountAssets } from "./useAccountAssets.js";
2
2
  export { useAccountWallet } from "./useAccountWallet.js";
3
3
  export { useAddTWSessionKey } from "./useAddTWSessionKey.js";
4
+ export { useAnalytics } from "./useAnalytics.js";
4
5
  export { useAuthentication } from "./useAuthentication.js";
5
6
  export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses.js";
6
7
  export { useB3EnsName } from "./useB3EnsName.js";
@@ -18,13 +19,13 @@ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
18
19
  export { useMediaQuery } from "./useMediaQuery.js";
19
20
  export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance.js";
20
21
  export { useOneBalance } from "./useOneBalance.js";
21
- export { useSimBalance } from "./useSimBalance.js";
22
22
  export { useProfile, useProfilePreference, } from "./useProfile.js";
23
23
  export { useQueryB3 } from "./useQueryB3.js";
24
24
  export { useQueryBSMNT } from "./useQueryBSMNT.js";
25
25
  export { useRemoveSessionKey } from "./useRemoveSessionKey.js";
26
26
  export { useRouter } from "./useRouter.js";
27
27
  export { useSearchParamsSSR } from "./useSearchParamsSSR.js";
28
+ export { useSimBalance } from "./useSimBalance.js";
28
29
  export { useSiwe } from "./useSiwe.js";
29
30
  export { useTokenBalance } from "./useTokenBalance.js";
30
31
  export { useTokenBalancesByChain } from "./useTokenBalancesByChain.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Analytics hook that provides sendAnalyticsEvent function
3
+ * Automatically includes user address from useAccountWallet
4
+ */
5
+ export declare function useAnalytics(): {
6
+ sendAnalyticsEvent: (eventName: string, parameters?: Record<string, any>) => void;
7
+ };
@@ -0,0 +1,26 @@
1
+ import { sendGA4Event } from "../../../global-account/utils/analytics.js";
2
+ import { useAccountWallet } from "./useAccountWallet.js";
3
+ /**
4
+ * Analytics hook that provides sendAnalyticsEvent function
5
+ * Automatically includes user address from useAccountWallet
6
+ */
7
+ export function useAnalytics() {
8
+ const { address } = useAccountWallet();
9
+ /**
10
+ * Sends an analytics event to Google Analytics 4
11
+ * @param eventName - The name of the event to track
12
+ * @param parameters - Additional parameters to include with the event
13
+ */
14
+ const sendAnalyticsEvent = (eventName, parameters) => {
15
+ // Merge user address with custom parameters
16
+ const eventData = {
17
+ user_address: address,
18
+ ...parameters,
19
+ };
20
+ // Send event to GA4 using utility function
21
+ sendGA4Event(eventName, eventData);
22
+ };
23
+ return {
24
+ sendAnalyticsEvent,
25
+ };
26
+ }
@@ -2,7 +2,11 @@ import { useQuery } from "@tanstack/react-query";
2
2
  async function fetchSimBalance(address) {
3
3
  if (!address)
4
4
  throw new Error("Address is required");
5
- const response = await fetch(`https://simdune-api.sean-430.workers.dev/?url=https://api.sim.dune.com/v1/evm/balances/${address}?metadata=logo&localkey=${process.env.PUBLIC_LOCAL_KEY}`);
5
+ let url = `https://simdune-api.sean-430.workers.dev/?url=https://api.sim.dune.com/v1/evm/balances/${address}?metadata=logo&chain_ids=mainnet`;
6
+ if (process.env.PUBLIC_LOCAL_KEY) {
7
+ url += `&localkey=${process.env.PUBLIC_LOCAL_KEY}`;
8
+ }
9
+ const response = await fetch(url);
6
10
  if (!response.ok) {
7
11
  throw new Error(`Failed to fetch balance: ${response.statusText}`);
8
12
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Load Google Analytics 4 script and initialize
3
+ */
4
+ export declare const loadGA4Script: () => void;
5
+ /**
6
+ * Send an analytics event to Google Analytics 4
7
+ * @param eventName - The name of the event to track
8
+ * @param parameters - Additional parameters to include with the event
9
+ */
10
+ export declare const sendGA4Event: (eventName: string, parameters?: Record<string, any>) => void;
@@ -0,0 +1,49 @@
1
+ const GA4_MEASUREMENT_ID = "G-Z67JCLMNZE";
2
+ /**
3
+ * Initialize Google Analytics 4
4
+ */
5
+ const initializeGA4 = () => {
6
+ // Only initialize in browser environment
7
+ if (typeof window === "undefined")
8
+ return;
9
+ // Create gtag function if it doesn't exist
10
+ if (!window.gtag) {
11
+ window.dataLayer = window.dataLayer || [];
12
+ window.gtag = function gtag() {
13
+ window.dataLayer.push(arguments);
14
+ };
15
+ }
16
+ // Configure GA4
17
+ window.gtag("js", new Date());
18
+ window.gtag("config", GA4_MEASUREMENT_ID, {
19
+ page_location: window.location.href,
20
+ page_hostname: window.location.hostname,
21
+ });
22
+ };
23
+ /**
24
+ * Load Google Analytics 4 script and initialize
25
+ */
26
+ export const loadGA4Script = () => {
27
+ if (typeof window === "undefined")
28
+ return;
29
+ // Check if script is already loaded
30
+ if (document.querySelector(`script[src*="${GA4_MEASUREMENT_ID}"]`))
31
+ return;
32
+ const script = document.createElement("script");
33
+ script.async = true;
34
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${GA4_MEASUREMENT_ID}`;
35
+ document.head.appendChild(script);
36
+ script.onload = initializeGA4;
37
+ };
38
+ /**
39
+ * Send an analytics event to Google Analytics 4
40
+ * @param eventName - The name of the event to track
41
+ * @param parameters - Additional parameters to include with the event
42
+ */
43
+ export const sendGA4Event = (eventName, parameters) => {
44
+ // Only send events in browser environment
45
+ if (typeof window === "undefined" || !window.gtag)
46
+ return;
47
+ // Send event to GA4
48
+ window.gtag("event", eventName, parameters || {});
49
+ };
@@ -1,6 +1,7 @@
1
1
  export { useAccountAssets } from "./useAccountAssets";
2
2
  export { useAccountWallet } from "./useAccountWallet";
3
3
  export { useAddTWSessionKey } from "./useAddTWSessionKey";
4
+ export { useAnalytics } from "./useAnalytics";
4
5
  export { useAuthentication } from "./useAuthentication";
5
6
  export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
6
7
  export { useB3EnsName } from "./useB3EnsName";
@@ -18,13 +19,13 @@ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
18
19
  export { useMediaQuery } from "./useMediaQuery";
19
20
  export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance";
20
21
  export { useOneBalance } from "./useOneBalance";
21
- export { useSimBalance } from "./useSimBalance";
22
22
  export { useProfile, useProfilePreference, type CombinedProfile, type PreferenceRequestBody, type Profile, } from "./useProfile";
23
23
  export { useQueryB3 } from "./useQueryB3";
24
24
  export { useQueryBSMNT } from "./useQueryBSMNT";
25
25
  export { useRemoveSessionKey } from "./useRemoveSessionKey";
26
26
  export { useRouter } from "./useRouter";
27
27
  export { useSearchParamsSSR } from "./useSearchParamsSSR";
28
+ export { useSimBalance } from "./useSimBalance";
28
29
  export { useSiwe } from "./useSiwe";
29
30
  export { useTokenBalance } from "./useTokenBalance";
30
31
  export { useTokenBalancesByChain } from "./useTokenBalancesByChain";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Analytics hook that provides sendAnalyticsEvent function
3
+ * Automatically includes user address from useAccountWallet
4
+ */
5
+ export declare function useAnalytics(): {
6
+ sendAnalyticsEvent: (eventName: string, parameters?: Record<string, any>) => void;
7
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Load Google Analytics 4 script and initialize
3
+ */
4
+ export declare const loadGA4Script: () => void;
5
+ /**
6
+ * Send an analytics event to Google Analytics 4
7
+ * @param eventName - The name of the event to track
8
+ * @param parameters - Additional parameters to include with the event
9
+ */
10
+ export declare const sendGA4Event: (eventName: string, parameters?: Record<string, any>) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.30-alpha.6",
3
+ "version": "0.0.30-alpha.8",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -1,6 +1,7 @@
1
1
  import { RelayKitProviderWrapper, TooltipProvider, useAuthStore } from "@b3dotfun/sdk/global-account/react";
2
2
  import { User } from "@b3dotfun/sdk/global-account/types/b3-api.types";
3
3
  import { PermissionsConfig } from "@b3dotfun/sdk/global-account/types/permissions";
4
+ import { loadGA4Script } from "@b3dotfun/sdk/global-account/utils/analytics";
4
5
  import { supportedChains } from "@b3dotfun/sdk/shared/constants/chains/supported";
5
6
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6
7
  import { useCallback, useEffect, useState } from "react";
@@ -60,6 +61,11 @@ export function B3Provider({
60
61
  style?: React.CSSProperties;
61
62
  };
62
63
  }) {
64
+ // Initialize Google Analytics on mount
65
+ useEffect(() => {
66
+ loadGA4Script();
67
+ }, []);
68
+
63
69
  return (
64
70
  <WagmiProvider config={wagmiConfig}>
65
71
  <QueryClientProvider client={queryClient}>
@@ -1,8 +1,9 @@
1
- import { ALL_CHAINS } from "@b3dotfun/sdk/anyspend";
1
+ import { ALL_CHAINS, getExplorerTxUrl } from "@b3dotfun/sdk/anyspend";
2
2
  import { ChainTokenIcon } from "@b3dotfun/sdk/anyspend/react/components/common/ChainTokenIcon";
3
3
  import {
4
4
  Button,
5
5
  TransitionPanel,
6
+ useAnalytics,
6
7
  useSimBalance,
7
8
  useUnifiedChainSwitchAndExecute,
8
9
  } from "@b3dotfun/sdk/global-account/react";
@@ -60,6 +61,9 @@ export function ContentTokens({ activeTab }: ContentTokensProps) {
60
61
  // === BLOCKCHAIN INTERACTION ===
61
62
  const { switchChainAndExecute } = useUnifiedChainSwitchAndExecute();
62
63
 
64
+ // === ANALYTICS ===
65
+ const { sendAnalyticsEvent } = useAnalytics();
66
+
63
67
  // === ADDRESS VALIDATION ===
64
68
  // Handle recipient address change with real-time validation using viem
65
69
  const handleRecipientAddressChange = (value: string) => {
@@ -159,6 +163,14 @@ export function ContentTokens({ activeTab }: ContentTokensProps) {
159
163
 
160
164
  const amountInWei = parseUnits(sendAmount, displayToken.decimals);
161
165
 
166
+ // Prepare analytics event data
167
+ const analyticsData = {
168
+ amount: sendAmount,
169
+ symbol: displayToken.symbol,
170
+ chain_id: displayToken.chain_id,
171
+ address: displayToken.address,
172
+ };
173
+
162
174
  try {
163
175
  const sendTokenData = encodeFunctionData({
164
176
  abi: erc20Abi,
@@ -173,10 +185,24 @@ export function ContentTokens({ activeTab }: ContentTokensProps) {
173
185
  });
174
186
 
175
187
  if (tx) {
188
+ // Track successful send
189
+ sendAnalyticsEvent("send_token_button_click", {
190
+ ...analyticsData,
191
+ success: true,
192
+ tx: getExplorerTxUrl(displayToken.chain_id, tx),
193
+ });
194
+
176
195
  // Reset form
177
196
  setSendAmount("");
178
197
  }
179
198
  } catch (error: any) {
199
+ // Track failed send
200
+ sendAnalyticsEvent("send_token_button_click", {
201
+ ...analyticsData,
202
+ success: false,
203
+ reason: error.message || "Unknown error",
204
+ });
205
+
180
206
  // Error
181
207
  toast.error(`Failed to send ${displayToken.symbol}: ${error.message || "Unknown error"}`);
182
208
  } finally {
@@ -1,6 +1,7 @@
1
1
  export { useAccountAssets } from "./useAccountAssets";
2
2
  export { useAccountWallet } from "./useAccountWallet";
3
3
  export { useAddTWSessionKey } from "./useAddTWSessionKey";
4
+ export { useAnalytics } from "./useAnalytics";
4
5
  export { useAuthentication } from "./useAuthentication";
5
6
  export { useB3BalanceFromAddresses } from "./useB3BalanceFromAddresses";
6
7
  export { useB3EnsName } from "./useB3EnsName";
@@ -18,7 +19,6 @@ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
18
19
  export { useMediaQuery } from "./useMediaQuery";
19
20
  export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance";
20
21
  export { useOneBalance } from "./useOneBalance";
21
- export { useSimBalance } from "./useSimBalance";
22
22
  export {
23
23
  useProfile,
24
24
  useProfilePreference,
@@ -31,6 +31,7 @@ export { useQueryBSMNT } from "./useQueryBSMNT";
31
31
  export { useRemoveSessionKey } from "./useRemoveSessionKey";
32
32
  export { useRouter } from "./useRouter";
33
33
  export { useSearchParamsSSR } from "./useSearchParamsSSR";
34
+ export { useSimBalance } from "./useSimBalance";
34
35
  export { useSiwe } from "./useSiwe";
35
36
  export { useTokenBalance } from "./useTokenBalance";
36
37
  export { useTokenBalancesByChain } from "./useTokenBalancesByChain";
@@ -0,0 +1,30 @@
1
+ import { sendGA4Event } from "@b3dotfun/sdk/global-account/utils/analytics";
2
+ import { useAccountWallet } from "./useAccountWallet";
3
+
4
+ /**
5
+ * Analytics hook that provides sendAnalyticsEvent function
6
+ * Automatically includes user address from useAccountWallet
7
+ */
8
+ export function useAnalytics() {
9
+ const { address } = useAccountWallet();
10
+
11
+ /**
12
+ * Sends an analytics event to Google Analytics 4
13
+ * @param eventName - The name of the event to track
14
+ * @param parameters - Additional parameters to include with the event
15
+ */
16
+ const sendAnalyticsEvent = (eventName: string, parameters?: Record<string, any>) => {
17
+ // Merge user address with custom parameters
18
+ const eventData = {
19
+ user_address: address,
20
+ ...parameters,
21
+ };
22
+
23
+ // Send event to GA4 using utility function
24
+ sendGA4Event(eventName, eventData);
25
+ };
26
+
27
+ return {
28
+ sendAnalyticsEvent,
29
+ };
30
+ }
@@ -29,9 +29,12 @@ export interface SimBalanceResponse {
29
29
  async function fetchSimBalance(address: string): Promise<SimBalanceResponse> {
30
30
  if (!address) throw new Error("Address is required");
31
31
 
32
- const response = await fetch(
33
- `https://simdune-api.sean-430.workers.dev/?url=https://api.sim.dune.com/v1/evm/balances/${address}?metadata=logo&localkey=${process.env.PUBLIC_LOCAL_KEY}`,
34
- );
32
+ let url = `https://simdune-api.sean-430.workers.dev/?url=https://api.sim.dune.com/v1/evm/balances/${address}?metadata=logo&chain_ids=mainnet`;
33
+ if (process.env.PUBLIC_LOCAL_KEY) {
34
+ url += `&localkey=${process.env.PUBLIC_LOCAL_KEY}`;
35
+ }
36
+
37
+ const response = await fetch(url);
35
38
 
36
39
  if (!response.ok) {
37
40
  throw new Error(`Failed to fetch balance: ${response.statusText}`);
@@ -0,0 +1,54 @@
1
+ const GA4_MEASUREMENT_ID = "G-Z67JCLMNZE";
2
+
3
+ /**
4
+ * Initialize Google Analytics 4
5
+ */
6
+ const initializeGA4 = () => {
7
+ // Only initialize in browser environment
8
+ if (typeof window === "undefined") return;
9
+
10
+ // Create gtag function if it doesn't exist
11
+ if (!window.gtag) {
12
+ window.dataLayer = window.dataLayer || [];
13
+ window.gtag = function gtag() {
14
+ window.dataLayer.push(arguments);
15
+ };
16
+ }
17
+
18
+ // Configure GA4
19
+ window.gtag("js", new Date());
20
+ window.gtag("config", GA4_MEASUREMENT_ID, {
21
+ page_location: window.location.href,
22
+ page_hostname: window.location.hostname,
23
+ });
24
+ };
25
+
26
+ /**
27
+ * Load Google Analytics 4 script and initialize
28
+ */
29
+ export const loadGA4Script = () => {
30
+ if (typeof window === "undefined") return;
31
+
32
+ // Check if script is already loaded
33
+ if (document.querySelector(`script[src*="${GA4_MEASUREMENT_ID}"]`)) return;
34
+
35
+ const script = document.createElement("script");
36
+ script.async = true;
37
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${GA4_MEASUREMENT_ID}`;
38
+ document.head.appendChild(script);
39
+
40
+ script.onload = initializeGA4;
41
+ };
42
+
43
+ /**
44
+ * Send an analytics event to Google Analytics 4
45
+ * @param eventName - The name of the event to track
46
+ * @param parameters - Additional parameters to include with the event
47
+ */
48
+ export const sendGA4Event = (eventName: string, parameters?: Record<string, any>) => {
49
+ // Only send events in browser environment
50
+ if (typeof window === "undefined" || !window.gtag) return;
51
+
52
+ // Send event to GA4
53
+ window.gtag("event", eventName, parameters || {});
54
+ };
@@ -1,7 +1,8 @@
1
- // Global window interface augmentations for AnySpend wallet providers
1
+ // Global window interface augmentations for B3 SDK
2
2
 
3
3
  declare global {
4
4
  interface Window {
5
+ // AnySpend wallet providers
5
6
  phantom?: {
6
7
  solana?: {
7
8
  isPhantom?: boolean;
@@ -9,6 +10,9 @@ declare global {
9
10
  signAndSendTransaction: (transaction: any) => Promise<{ signature: string }>;
10
11
  };
11
12
  };
13
+ // Google Analytics 4
14
+ gtag: (...args: any[]) => void;
15
+ dataLayer: any[];
12
16
  }
13
17
  }
14
18