@b3dotfun/sdk 0.0.40-alpha.26 → 0.0.40-alpha.27

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 (47) hide show
  1. package/dist/cjs/anyspend/react/components/AnyspendSignatureMint.js +1 -1
  2. package/dist/cjs/anyspend/services/anyspend.d.ts +1 -1
  3. package/dist/cjs/anyspend/services/anyspend.js +2 -0
  4. package/dist/cjs/bondkit/components/TradingView.js +3 -4
  5. package/dist/cjs/bondkit/swapService.js +3 -0
  6. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +18 -12
  7. package/dist/cjs/global-account/react/hooks/useAccountAssets.js +5 -2
  8. package/dist/cjs/global-account/react/hooks/useAuthentication.js +1 -1
  9. package/dist/cjs/global-account/react/hooks/useQueryB3.js +5 -2
  10. package/dist/cjs/global-account/react/hooks/useQueryBSMNT.js +5 -2
  11. package/dist/cjs/global-account/react/hooks/useTokenBalancesByChain.js +7 -1
  12. package/dist/cjs/global-account/react/hooks/useTokenFromUrl.js +2 -2
  13. package/dist/cjs/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +1 -2
  14. package/dist/cjs/shared/utils/fetchBalances.d.ts +1 -1
  15. package/dist/esm/anyspend/react/components/AnyspendSignatureMint.js +1 -1
  16. package/dist/esm/anyspend/services/anyspend.d.ts +1 -1
  17. package/dist/esm/anyspend/services/anyspend.js +2 -0
  18. package/dist/esm/bondkit/components/TradingView.js +3 -4
  19. package/dist/esm/bondkit/swapService.js +5 -2
  20. package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +18 -12
  21. package/dist/esm/global-account/react/hooks/useAccountAssets.js +2 -2
  22. package/dist/esm/global-account/react/hooks/useAuthentication.js +1 -1
  23. package/dist/esm/global-account/react/hooks/useQueryB3.js +5 -2
  24. package/dist/esm/global-account/react/hooks/useQueryBSMNT.js +5 -2
  25. package/dist/esm/global-account/react/hooks/useTokenBalancesByChain.js +4 -1
  26. package/dist/esm/global-account/react/hooks/useTokenFromUrl.js +2 -2
  27. package/dist/esm/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +2 -3
  28. package/dist/esm/shared/utils/fetchBalances.d.ts +1 -1
  29. package/dist/types/anyspend/services/anyspend.d.ts +1 -1
  30. package/dist/types/shared/utils/fetchBalances.d.ts +1 -1
  31. package/package.json +1 -1
  32. package/src/anyspend/react/components/AnyspendSignatureMint.tsx +1 -1
  33. package/src/anyspend/react/hooks/useAnyspendOrderAndTransactions.ts +1 -1
  34. package/src/anyspend/react/hooks/useCoinbaseOnrampOptions.ts +1 -1
  35. package/src/anyspend/services/anyspend.ts +3 -1
  36. package/src/bondkit/components/TradingView.tsx +3 -5
  37. package/src/bondkit/swapService.ts +10 -7
  38. package/src/global-account/react/components/B3Provider/B3Provider.tsx +20 -16
  39. package/src/global-account/react/hooks/useAccountAssets.ts +4 -3
  40. package/src/global-account/react/hooks/useAuthentication.ts +1 -1
  41. package/src/global-account/react/hooks/useOneBalance.tsx +1 -1
  42. package/src/global-account/react/hooks/useQueryB3.ts +22 -15
  43. package/src/global-account/react/hooks/useQueryBSMNT.ts +22 -15
  44. package/src/global-account/react/hooks/useTokenBalancesByChain.tsx +4 -1
  45. package/src/global-account/react/hooks/useTokenFromUrl.tsx +2 -2
  46. package/src/global-account/react/hooks/useUnifiedChainSwitchAndExecute.ts +2 -3
  47. package/src/shared/utils/fetchBalances.ts +1 -1
@@ -72,7 +72,7 @@ function AnyspendSignatureMint({ loadOrder, mode = "modal", signatureData, image
72
72
  }
73
73
  const encodedData = generateEncodedDataForSignatureMint(signatureData);
74
74
  const price = (0, viem_1.parseEther)(signatureData.payload.price?.toString() || "0");
75
- return ((0, jsx_runtime_1.jsx)(AnySpendCustom_1.AnySpendCustom, { loadOrder: loadOrder, mode: mode, recipientAddress: signatureData.payload.to, orderType: "custom", dstChainId: signatureData.collection.chainId, dstToken: dstToken, dstAmount: price.toString(), contractAddress: signatureData.collection.address, encodedData: encodedData, metadata: {
75
+ return ((0, jsx_runtime_1.jsx)(AnySpendCustom_1.AnySpendCustom, { loadOrder: loadOrder, mode: mode, recipientAddress: signatureData.payload.to, orderType: "custom", dstChainId: signatureData.collection.chainId, dstToken: dstToken, dstAmount: price.toString(), contractAddress: signatureData.collection.address || "", encodedData: encodedData, metadata: {
76
76
  action: "Signature Mint",
77
77
  }, header: header, onSuccess: onSuccess, showRecipient: true }));
78
78
  }
@@ -28,7 +28,7 @@ export declare const anyspendService: {
28
28
  }>;
29
29
  getOrderAndTransactions: (orderId: string | undefined) => Promise<GetOrderAndTxsResponse>;
30
30
  getOrderHistory: (creatorAddress: string | undefined, limit?: number, offset?: number) => Promise<GetOrderHistoryResponse>;
31
- getCoinbaseOnrampOptions: (country: string, visitorData?: VisitorData) => Promise<GetCoinbaseOnrampOptionsResponse>;
31
+ getCoinbaseOnrampOptions: (country: string | undefined, visitorData?: VisitorData) => Promise<GetCoinbaseOnrampOptionsResponse>;
32
32
  checkStripeSupport: (usdAmount?: string, visitorData?: VisitorData) => Promise<{
33
33
  stripeOnramp: boolean;
34
34
  stripeWeb2: components["schemas"]["StripeWeb2Support"];
@@ -69,6 +69,7 @@ exports.anyspendService = {
69
69
  return data;
70
70
  },
71
71
  getOrderAndTransactions: async (orderId) => {
72
+ (0, invariant_1.default)(orderId, "orderId is required");
72
73
  const response = await fetch(`${constants_1.ANYSPEND_MAINNET_BASE_URL}/orders/${orderId}`);
73
74
  const data = await response.json();
74
75
  return data;
@@ -86,6 +87,7 @@ exports.anyspendService = {
86
87
  return data;
87
88
  },
88
89
  getCoinbaseOnrampOptions: async (country, visitorData) => {
90
+ (0, invariant_1.default)(country, "country is required");
89
91
  const params = new URLSearchParams({
90
92
  country,
91
93
  // include fingerprintId and requestId in the query params
@@ -2,16 +2,15 @@
2
2
  "use client";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
- const cdn_loader_1 = require("./utils/cdn-loader");
6
- const format_1 = require("./utils/format");
7
5
  const lucide_react_1 = require("lucide-react");
8
6
  const react_1 = require("react");
7
+ const cdn_loader_1 = require("./utils/cdn-loader");
8
+ const format_1 = require("./utils/format");
9
9
  // TradingView will be available on window after loading from CDN
10
10
  // Datafeed will be implemented inline
11
11
  // Mock loading overlay - replace with your actual loading component
12
12
  const GifLoadingOverlay = ({ className }) => ((0, jsx_runtime_1.jsx)("div", { className: `absolute inset-0 flex items-center justify-center bg-white/50 backdrop-blur-sm ${className || ""}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "text-secondary-grey h-8 w-8 animate-spin" }) }));
13
13
  const TradingView = ({ className, tokenAddress, tokenSymbol }) => {
14
- const theme = "light";
15
14
  // Use token info for the current trade
16
15
  const currentTrade = {
17
16
  product_id: tokenAddress && tokenSymbol ? `${tokenSymbol}-${tokenAddress}` : "BONDKIT",
@@ -247,7 +246,7 @@ const TradingView = ({ className, tokenAddress, tokenSymbol }) => {
247
246
  tvWidgetRef.current = null;
248
247
  }
249
248
  };
250
- }, [theme, librariesLoaded]);
249
+ }, [librariesLoaded, currentTrade?.product_id, tradingViewDefaultInterval, tradingViewTimezone]);
251
250
  (0, react_1.useEffect)(() => {
252
251
  if (chartLoaded &&
253
252
  currentTrade?.product_id &&
@@ -190,6 +190,9 @@ class BondkitSwapService {
190
190
  */
191
191
  async getV4Config() {
192
192
  await this.initializeV4Config();
193
+ if (!this.v4Config) {
194
+ throw new Error("Failed to initialize V4 configuration");
195
+ }
193
196
  return this.v4Config;
194
197
  }
195
198
  /**
@@ -52,22 +52,28 @@ function B3Provider({ theme = "light", children, accountOverride, environment, a
52
52
  client: thirdweb_1.client,
53
53
  };
54
54
  }, [partnerId]);
55
+ // Stringify rpcUrls for stable comparison to prevent wagmiConfig recreation
56
+ // when parent component passes new object references with same content
57
+ const rpcUrlsString = (0, react_2.useMemo)(() => (rpcUrls ? JSON.stringify(rpcUrls) : undefined), [rpcUrls]);
55
58
  /**
56
59
  * Creates wagmi config with optional custom RPC URLs
57
60
  * @param rpcUrls - Optional mapping of chain IDs to RPC URLs
58
61
  */
59
- const wagmiConfig = (0, react_2.useMemo)(() => (0, wagmi_1.createConfig)({
60
- chains: [supported_1.supportedChains[0], ...supported_1.supportedChains.slice(1)],
61
- transports: Object.fromEntries(supported_1.supportedChains.map(chain => [chain.id, (0, wagmi_1.http)(rpcUrls?.[chain.id])])),
62
- connectors: [
63
- (0, wagmi_adapter_1.inAppWalletConnector)({
64
- ...(ecocystemConfig || {}),
65
- client: thirdweb_1.client,
66
- }),
67
- // injected(),
68
- // coinbaseWallet({ appName: "HypeDuel" }),
69
- ],
70
- }), [partnerId]);
62
+ const wagmiConfig = (0, react_2.useMemo)(() => {
63
+ const parsedRpcUrls = rpcUrlsString ? JSON.parse(rpcUrlsString) : undefined;
64
+ return (0, wagmi_1.createConfig)({
65
+ chains: [supported_1.supportedChains[0], ...supported_1.supportedChains.slice(1)],
66
+ transports: Object.fromEntries(supported_1.supportedChains.map(chain => [chain.id, (0, wagmi_1.http)(parsedRpcUrls?.[chain.id])])),
67
+ connectors: [
68
+ (0, wagmi_adapter_1.inAppWalletConnector)({
69
+ ...(ecocystemConfig || {}),
70
+ client: thirdweb_1.client,
71
+ }),
72
+ // injected(),
73
+ // coinbaseWallet({ appName: "HypeDuel" }),
74
+ ],
75
+ });
76
+ }, [ecocystemConfig, rpcUrlsString]);
71
77
  return ((0, jsx_runtime_1.jsx)(react_3.ThirdwebProvider, { children: (0, jsx_runtime_1.jsx)(wagmi_1.WagmiProvider, { config: wagmiConfig, children: (0, jsx_runtime_1.jsx)(react_query_1.QueryClientProvider, { client: queryClient, children: (0, jsx_runtime_1.jsx)(react_1.TooltipProvider, { children: (0, jsx_runtime_1.jsx)(InnerProvider, { accountOverride: accountOverride, environment: environment, theme: theme, automaticallySetFirstEoa: !!automaticallySetFirstEoa, clientType: clientType, 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 })] }) }) }) }) }) }));
72
78
  }
73
79
  /**
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.useAccountAssets = useAccountAssets;
4
7
  const simplehash_1 = require("../../../shared/utils/simplehash");
5
8
  const react_query_1 = require("@tanstack/react-query");
9
+ const invariant_1 = __importDefault(require("invariant"));
6
10
  async function fetchAccountAssets(address) {
7
- if (!address)
8
- throw new Error("Address is required");
11
+ (0, invariant_1.default)(address, "Address is required");
9
12
  const [nftResponse] = await Promise.all([
10
13
  (0, simplehash_1.fetchSimpleHashData)(`/v0/nfts/owners`, {
11
14
  chains: "b3,b3-sepolia,base",
@@ -98,7 +98,7 @@ function useAuthentication(partnerId, loginWithSiwe) {
98
98
  setIsAuthenticating(false);
99
99
  }
100
100
  useAutoConnectLoadingPrevious.current = useAutoConnectLoading;
101
- }, [useAutoConnectLoading]);
101
+ }, [useAutoConnectLoading, hasStartedConnecting, setIsAuthenticating]);
102
102
  // Ensure isAuthenticating stays true until we're fully ready
103
103
  (0, react_2.useEffect)(() => {
104
104
  if (useAutoConnectLoading) {
@@ -30,11 +30,14 @@ fetchInitially = true) {
30
30
  finally {
31
31
  setIsLoading(false);
32
32
  }
33
- }, []);
33
+ }, [service, method]);
34
+ // Serialize params for stable comparison
35
+ const paramsJson = JSON.stringify(params);
34
36
  (0, react_1.useEffect)(() => {
35
37
  if (fetchInitially) {
36
38
  runQuery(params);
37
39
  }
38
- }, [runQuery, fetchInitially, JSON.stringify(params)]);
40
+ // eslint-disable-next-line react-hooks/exhaustive-deps
41
+ }, [runQuery, fetchInitially, paramsJson]);
39
42
  return { data, error, isLoading, runQuery };
40
43
  }
@@ -30,11 +30,14 @@ fetchInitially = true) {
30
30
  finally {
31
31
  setIsLoading(false);
32
32
  }
33
- }, []);
33
+ }, [service, method]);
34
+ // Serialize params for stable comparison
35
+ const paramsJson = JSON.stringify(params);
34
36
  (0, react_1.useEffect)(() => {
35
37
  if (fetchInitially) {
36
38
  runQuery(params);
37
39
  }
38
- }, [runQuery, fetchInitially, JSON.stringify(params)]);
40
+ // eslint-disable-next-line react-hooks/exhaustive-deps
41
+ }, [runQuery, fetchInitially, paramsJson]);
39
42
  return { data, error, isLoading, runQuery };
40
43
  }
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  "use client";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  exports.useTokenBalancesByChain = useTokenBalancesByChain;
5
8
  const thirdweb_insights_1 = require("../../../shared/utils/thirdweb-insights");
@@ -8,6 +11,7 @@ const b3Chain_1 = require("../../../shared/constants/chains/b3Chain");
8
11
  const chains_1 = require("../../../shared/utils/chains");
9
12
  const thirdweb_1 = require("../../../shared/utils/thirdweb");
10
13
  const wallets_1 = require("thirdweb/wallets");
14
+ const invariant_1 = __importDefault(require("invariant"));
11
15
  function useTokenBalancesByChain({ address, chainsIds, enabled = true, }) {
12
16
  // Normalize chains to array
13
17
  const chainIds = Array.isArray(chainsIds) ? chainsIds : [chainsIds];
@@ -19,10 +23,12 @@ function useTokenBalancesByChain({ address, chainsIds, enabled = true, }) {
19
23
  const [nativeTokens, fungibleResponse] = await Promise.all([
20
24
  // Fetch native token balances
21
25
  Promise.all(chainIds.map(async (chainId) => {
26
+ const chain = (0, chains_1.getChainById)(chainId);
27
+ (0, invariant_1.default)(chain, "Chain is required");
22
28
  const walletBalance = await (0, wallets_1.getWalletBalance)({
23
29
  address,
24
30
  client: thirdweb_1.client,
25
- chain: (0, b3Chain_1.viemToThirdwebChain)((0, chains_1.getChainById)(chainId)),
31
+ chain: (0, b3Chain_1.viemToThirdwebChain)(chain),
26
32
  });
27
33
  return {
28
34
  ...walletBalance,
@@ -38,7 +38,7 @@ function useTokenFromUrl({ defaultToken, prefix }) {
38
38
  const network = chainIdParam ? (0, supported_1.getCoingeckoChainInfo)(Number(chainIdParam)).coingecko_id : "";
39
39
  const { data: tokenInfo, isError } = (0, react_query_1.useQuery)({
40
40
  queryKey: ["tokenInfo", network, currencyParam],
41
- queryFn: () => fetchTokenInfo(network, currencyParam),
41
+ queryFn: () => fetchTokenInfo(network, currencyParam || ""),
42
42
  enabled: shouldFetchToken,
43
43
  staleTime: Infinity,
44
44
  gcTime: Infinity,
@@ -51,7 +51,7 @@ function useTokenFromUrl({ defaultToken, prefix }) {
51
51
  if (isError || !tokenInfo) {
52
52
  return {
53
53
  ...defaultToken,
54
- address: currencyParam,
54
+ address: currencyParam || "",
55
55
  chainId: Number(chainIdParam),
56
56
  };
57
57
  }
@@ -23,7 +23,6 @@ const partnerId = String(process.env.PUBLIC_THIRDWEB_PARTNER_ID ||
23
23
  process.env.NEXT_PUBLIC_GLOBAL_ACCOUNTS_PARTNER_ID);
24
24
  (0, invariant_1.default)(partnerId, "Partner ID is required");
25
25
  function useUnifiedChainSwitchAndExecute() {
26
- const { data: walletClient } = (0, wagmi_1.useWalletClient)();
27
26
  const { switchChainAsync } = (0, wagmi_1.useSwitchChain)();
28
27
  const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = (0, react_1.useState)(false);
29
28
  const activeWallet = (0, react_2.useActiveWallet)();
@@ -104,7 +103,7 @@ function useUnifiedChainSwitchAndExecute() {
104
103
  finally {
105
104
  setIsSwitchingOrExecuting(false);
106
105
  }
107
- }, [walletClient, switchChainAsync]);
106
+ }, [connectedEOAWallet, activeWallet, switchChainAsync]);
108
107
  // Handle AA wallet transaction (no chain switch needed for AA)
109
108
  const handleAASendTransaction = (0, react_1.useCallback)(async (targetChainId, params) => {
110
109
  if (!aaAccount) {
@@ -13,4 +13,4 @@ export interface AssetBalance {
13
13
  totalBalance: string;
14
14
  chainBalances: ChainBalance[];
15
15
  }
16
- export declare const fetchBalances: (address: string, testnet?: boolean) => Promise<AssetBalance[]>;
16
+ export declare const fetchBalances: (address: string | undefined, testnet?: boolean) => Promise<AssetBalance[]>;
@@ -66,7 +66,7 @@ export function AnyspendSignatureMint({ loadOrder, mode = "modal", signatureData
66
66
  }
67
67
  const encodedData = generateEncodedDataForSignatureMint(signatureData);
68
68
  const price = parseEther(signatureData.payload.price?.toString() || "0");
69
- return (_jsx(AnySpendCustom, { loadOrder: loadOrder, mode: mode, recipientAddress: signatureData.payload.to, orderType: "custom", dstChainId: signatureData.collection.chainId, dstToken: dstToken, dstAmount: price.toString(), contractAddress: signatureData.collection.address, encodedData: encodedData, metadata: {
69
+ return (_jsx(AnySpendCustom, { loadOrder: loadOrder, mode: mode, recipientAddress: signatureData.payload.to, orderType: "custom", dstChainId: signatureData.collection.chainId, dstToken: dstToken, dstAmount: price.toString(), contractAddress: signatureData.collection.address || "", encodedData: encodedData, metadata: {
70
70
  action: "Signature Mint",
71
71
  }, header: header, onSuccess: onSuccess, showRecipient: true }));
72
72
  }
@@ -28,7 +28,7 @@ export declare const anyspendService: {
28
28
  }>;
29
29
  getOrderAndTransactions: (orderId: string | undefined) => Promise<GetOrderAndTxsResponse>;
30
30
  getOrderHistory: (creatorAddress: string | undefined, limit?: number, offset?: number) => Promise<GetOrderHistoryResponse>;
31
- getCoinbaseOnrampOptions: (country: string, visitorData?: VisitorData) => Promise<GetCoinbaseOnrampOptionsResponse>;
31
+ getCoinbaseOnrampOptions: (country: string | undefined, visitorData?: VisitorData) => Promise<GetCoinbaseOnrampOptionsResponse>;
32
32
  checkStripeSupport: (usdAmount?: string, visitorData?: VisitorData) => Promise<{
33
33
  stripeOnramp: boolean;
34
34
  stripeWeb2: components["schemas"]["StripeWeb2Support"];
@@ -63,6 +63,7 @@ export const anyspendService = {
63
63
  return data;
64
64
  },
65
65
  getOrderAndTransactions: async (orderId) => {
66
+ invariant(orderId, "orderId is required");
66
67
  const response = await fetch(`${ANYSPEND_MAINNET_BASE_URL}/orders/${orderId}`);
67
68
  const data = await response.json();
68
69
  return data;
@@ -80,6 +81,7 @@ export const anyspendService = {
80
81
  return data;
81
82
  },
82
83
  getCoinbaseOnrampOptions: async (country, visitorData) => {
84
+ invariant(country, "country is required");
83
85
  const params = new URLSearchParams({
84
86
  country,
85
87
  // include fingerprintId and requestId in the query params
@@ -1,15 +1,14 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { loadScriptFromCDN } from "./utils/cdn-loader.js";
4
- import { formatNumberSmall } from "./utils/format.js";
5
3
  import { Loader2 } from "lucide-react";
6
4
  import { useEffect, useRef, useState } from "react";
5
+ import { loadScriptFromCDN } from "./utils/cdn-loader.js";
6
+ import { formatNumberSmall } from "./utils/format.js";
7
7
  // TradingView will be available on window after loading from CDN
8
8
  // Datafeed will be implemented inline
9
9
  // Mock loading overlay - replace with your actual loading component
10
10
  const GifLoadingOverlay = ({ className }) => (_jsx("div", { className: `absolute inset-0 flex items-center justify-center bg-white/50 backdrop-blur-sm ${className || ""}`, children: _jsx(Loader2, { className: "text-secondary-grey h-8 w-8 animate-spin" }) }));
11
11
  const TradingView = ({ className, tokenAddress, tokenSymbol }) => {
12
- const theme = "light";
13
12
  // Use token info for the current trade
14
13
  const currentTrade = {
15
14
  product_id: tokenAddress && tokenSymbol ? `${tokenSymbol}-${tokenAddress}` : "BONDKIT",
@@ -245,7 +244,7 @@ const TradingView = ({ className, tokenAddress, tokenSymbol }) => {
245
244
  tvWidgetRef.current = null;
246
245
  }
247
246
  };
248
- }, [theme, librariesLoaded]);
247
+ }, [librariesLoaded, currentTrade?.product_id, tradingViewDefaultInterval, tradingViewTimezone]);
249
248
  useEffect(() => {
250
249
  if (chartLoaded &&
251
250
  currentTrade?.product_id &&
@@ -1,6 +1,6 @@
1
- import { parseUnits, formatUnits, encodeAbiParameters, parseAbiParameters, getContract, createPublicClient, http, } from "viem";
1
+ import { createPublicClient, encodeAbiParameters, formatUnits, getContract, http, parseAbiParameters, parseUnits, } from "viem";
2
2
  import { base } from "viem/chains";
3
- import { UniversalRouterAddress, QuoterAddress, Permit2Address, BaseMainnetRpcUrl } from "./constants.js";
3
+ import { BaseMainnetRpcUrl, Permit2Address, QuoterAddress, UniversalRouterAddress } from "./constants.js";
4
4
  // Minimal ABIs needed for swap functionality
5
5
  const UNIVERSAL_ROUTER_ABI = [
6
6
  {
@@ -187,6 +187,9 @@ export class BondkitSwapService {
187
187
  */
188
188
  async getV4Config() {
189
189
  await this.initializeV4Config();
190
+ if (!this.v4Config) {
191
+ throw new Error("Failed to initialize V4 configuration");
192
+ }
190
193
  return this.v4Config;
191
194
  }
192
195
  /**
@@ -48,22 +48,28 @@ export function B3Provider({ theme = "light", children, accountOverride, environ
48
48
  client,
49
49
  };
50
50
  }, [partnerId]);
51
+ // Stringify rpcUrls for stable comparison to prevent wagmiConfig recreation
52
+ // when parent component passes new object references with same content
53
+ const rpcUrlsString = useMemo(() => (rpcUrls ? JSON.stringify(rpcUrls) : undefined), [rpcUrls]);
51
54
  /**
52
55
  * Creates wagmi config with optional custom RPC URLs
53
56
  * @param rpcUrls - Optional mapping of chain IDs to RPC URLs
54
57
  */
55
- const wagmiConfig = useMemo(() => createConfig({
56
- chains: [supportedChains[0], ...supportedChains.slice(1)],
57
- transports: Object.fromEntries(supportedChains.map(chain => [chain.id, http(rpcUrls?.[chain.id])])),
58
- connectors: [
59
- inAppWalletConnector({
60
- ...(ecocystemConfig || {}),
61
- client,
62
- }),
63
- // injected(),
64
- // coinbaseWallet({ appName: "HypeDuel" }),
65
- ],
66
- }), [partnerId]);
58
+ const wagmiConfig = useMemo(() => {
59
+ const parsedRpcUrls = rpcUrlsString ? JSON.parse(rpcUrlsString) : undefined;
60
+ return createConfig({
61
+ chains: [supportedChains[0], ...supportedChains.slice(1)],
62
+ transports: Object.fromEntries(supportedChains.map(chain => [chain.id, http(parsedRpcUrls?.[chain.id])])),
63
+ connectors: [
64
+ inAppWalletConnector({
65
+ ...(ecocystemConfig || {}),
66
+ client,
67
+ }),
68
+ // injected(),
69
+ // coinbaseWallet({ appName: "HypeDuel" }),
70
+ ],
71
+ });
72
+ }, [ecocystemConfig, rpcUrlsString]);
67
73
  return (_jsx(ThirdwebProvider, { children: _jsx(WagmiProvider, { config: wagmiConfig, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(TooltipProvider, { children: _jsx(InnerProvider, { accountOverride: accountOverride, environment: environment, theme: theme, automaticallySetFirstEoa: !!automaticallySetFirstEoa, clientType: clientType, children: _jsxs(RelayKitProviderWrapper, { simDuneApiKey: simDuneApiKey, children: [children, _jsx(StyleRoot, { id: "b3-root" }), _jsx(Toaster, { theme: theme, position: toaster?.position, style: toaster?.style })] }) }) }) }) }) }));
68
74
  }
69
75
  /**
@@ -1,8 +1,8 @@
1
1
  import { fetchSimpleHashData } from "../../../shared/utils/simplehash.js";
2
2
  import { useQuery } from "@tanstack/react-query";
3
+ import invariant from "invariant";
3
4
  async function fetchAccountAssets(address) {
4
- if (!address)
5
- throw new Error("Address is required");
5
+ invariant(address, "Address is required");
6
6
  const [nftResponse] = await Promise.all([
7
7
  fetchSimpleHashData(`/v0/nfts/owners`, {
8
8
  chains: "b3,b3-sepolia,base",
@@ -92,7 +92,7 @@ export function useAuthentication(partnerId, loginWithSiwe) {
92
92
  setIsAuthenticating(false);
93
93
  }
94
94
  useAutoConnectLoadingPrevious.current = useAutoConnectLoading;
95
- }, [useAutoConnectLoading]);
95
+ }, [useAutoConnectLoading, hasStartedConnecting, setIsAuthenticating]);
96
96
  // Ensure isAuthenticating stays true until we're fully ready
97
97
  useEffect(() => {
98
98
  if (useAutoConnectLoading) {
@@ -24,11 +24,14 @@ fetchInitially = true) {
24
24
  finally {
25
25
  setIsLoading(false);
26
26
  }
27
- }, []);
27
+ }, [service, method]);
28
+ // Serialize params for stable comparison
29
+ const paramsJson = JSON.stringify(params);
28
30
  useEffect(() => {
29
31
  if (fetchInitially) {
30
32
  runQuery(params);
31
33
  }
32
- }, [runQuery, fetchInitially, JSON.stringify(params)]);
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ }, [runQuery, fetchInitially, paramsJson]);
33
36
  return { data, error, isLoading, runQuery };
34
37
  }
@@ -24,11 +24,14 @@ fetchInitially = true) {
24
24
  finally {
25
25
  setIsLoading(false);
26
26
  }
27
- }, []);
27
+ }, [service, method]);
28
+ // Serialize params for stable comparison
29
+ const paramsJson = JSON.stringify(params);
28
30
  useEffect(() => {
29
31
  if (fetchInitially) {
30
32
  runQuery(params);
31
33
  }
32
- }, [runQuery, fetchInitially, JSON.stringify(params)]);
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ }, [runQuery, fetchInitially, paramsJson]);
33
36
  return { data, error, isLoading, runQuery };
34
37
  }
@@ -5,6 +5,7 @@ import { viemToThirdwebChain } from "../../../shared/constants/chains/b3Chain.js
5
5
  import { getChainById } from "../../../shared/utils/chains.js";
6
6
  import { client } from "../../../shared/utils/thirdweb.js";
7
7
  import { getWalletBalance } from "thirdweb/wallets";
8
+ import invariant from "invariant";
8
9
  export function useTokenBalancesByChain({ address, chainsIds, enabled = true, }) {
9
10
  // Normalize chains to array
10
11
  const chainIds = Array.isArray(chainsIds) ? chainsIds : [chainsIds];
@@ -16,10 +17,12 @@ export function useTokenBalancesByChain({ address, chainsIds, enabled = true, })
16
17
  const [nativeTokens, fungibleResponse] = await Promise.all([
17
18
  // Fetch native token balances
18
19
  Promise.all(chainIds.map(async (chainId) => {
20
+ const chain = getChainById(chainId);
21
+ invariant(chain, "Chain is required");
19
22
  const walletBalance = await getWalletBalance({
20
23
  address,
21
24
  client,
22
- chain: viemToThirdwebChain(getChainById(chainId)),
25
+ chain: viemToThirdwebChain(chain),
23
26
  });
24
27
  return {
25
28
  ...walletBalance,
@@ -34,7 +34,7 @@ export function useTokenFromUrl({ defaultToken, prefix }) {
34
34
  const network = chainIdParam ? getCoingeckoChainInfo(Number(chainIdParam)).coingecko_id : "";
35
35
  const { data: tokenInfo, isError } = useQuery({
36
36
  queryKey: ["tokenInfo", network, currencyParam],
37
- queryFn: () => fetchTokenInfo(network, currencyParam),
37
+ queryFn: () => fetchTokenInfo(network, currencyParam || ""),
38
38
  enabled: shouldFetchToken,
39
39
  staleTime: Infinity,
40
40
  gcTime: Infinity,
@@ -47,7 +47,7 @@ export function useTokenFromUrl({ defaultToken, prefix }) {
47
47
  if (isError || !tokenInfo) {
48
48
  return {
49
49
  ...defaultToken,
50
- address: currencyParam,
50
+ address: currencyParam || "",
51
51
  chainId: Number(chainIdParam),
52
52
  };
53
53
  }
@@ -8,7 +8,7 @@ import { toast } from "sonner";
8
8
  import { prepareTransaction, sendTransaction as twSendTransaction } from "thirdweb";
9
9
  import { useActiveWallet } from "thirdweb/react";
10
10
  import { isAddress } from "viem";
11
- import { useSwitchChain, useWalletClient } from "wagmi";
11
+ import { useSwitchChain } from "wagmi";
12
12
  import { useB3 } from "../components/index.js";
13
13
  import { useAccountWallet } from "./useAccountWallet.js";
14
14
  const partnerId = String(process.env.PUBLIC_THIRDWEB_PARTNER_ID ||
@@ -17,7 +17,6 @@ const partnerId = String(process.env.PUBLIC_THIRDWEB_PARTNER_ID ||
17
17
  process.env.NEXT_PUBLIC_GLOBAL_ACCOUNTS_PARTNER_ID);
18
18
  invariant(partnerId, "Partner ID is required");
19
19
  export function useUnifiedChainSwitchAndExecute() {
20
- const { data: walletClient } = useWalletClient();
21
20
  const { switchChainAsync } = useSwitchChain();
22
21
  const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = useState(false);
23
22
  const activeWallet = useActiveWallet();
@@ -98,7 +97,7 @@ export function useUnifiedChainSwitchAndExecute() {
98
97
  finally {
99
98
  setIsSwitchingOrExecuting(false);
100
99
  }
101
- }, [walletClient, switchChainAsync]);
100
+ }, [connectedEOAWallet, activeWallet, switchChainAsync]);
102
101
  // Handle AA wallet transaction (no chain switch needed for AA)
103
102
  const handleAASendTransaction = useCallback(async (targetChainId, params) => {
104
103
  if (!aaAccount) {
@@ -13,4 +13,4 @@ export interface AssetBalance {
13
13
  totalBalance: string;
14
14
  chainBalances: ChainBalance[];
15
15
  }
16
- export declare const fetchBalances: (address: string, testnet?: boolean) => Promise<AssetBalance[]>;
16
+ export declare const fetchBalances: (address: string | undefined, testnet?: boolean) => Promise<AssetBalance[]>;
@@ -28,7 +28,7 @@ export declare const anyspendService: {
28
28
  }>;
29
29
  getOrderAndTransactions: (orderId: string | undefined) => Promise<GetOrderAndTxsResponse>;
30
30
  getOrderHistory: (creatorAddress: string | undefined, limit?: number, offset?: number) => Promise<GetOrderHistoryResponse>;
31
- getCoinbaseOnrampOptions: (country: string, visitorData?: VisitorData) => Promise<GetCoinbaseOnrampOptionsResponse>;
31
+ getCoinbaseOnrampOptions: (country: string | undefined, visitorData?: VisitorData) => Promise<GetCoinbaseOnrampOptionsResponse>;
32
32
  checkStripeSupport: (usdAmount?: string, visitorData?: VisitorData) => Promise<{
33
33
  stripeOnramp: boolean;
34
34
  stripeWeb2: components["schemas"]["StripeWeb2Support"];
@@ -13,4 +13,4 @@ export interface AssetBalance {
13
13
  totalBalance: string;
14
14
  chainBalances: ChainBalance[];
15
15
  }
16
- export declare const fetchBalances: (address: string, testnet?: boolean) => Promise<AssetBalance[]>;
16
+ export declare const fetchBalances: (address: string | undefined, testnet?: boolean) => Promise<AssetBalance[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.40-alpha.26",
3
+ "version": "0.0.40-alpha.27",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -141,7 +141,7 @@ export function AnyspendSignatureMint({
141
141
  dstChainId={signatureData.collection.chainId}
142
142
  dstToken={dstToken}
143
143
  dstAmount={price.toString()}
144
- contractAddress={signatureData.collection.address!}
144
+ contractAddress={signatureData.collection.address || ""}
145
145
  encodedData={encodedData}
146
146
  metadata={{
147
147
  action: "Signature Mint",
@@ -35,7 +35,7 @@ export function useAnyspendOrderAndTransactions(orderId: string | undefined) {
35
35
 
36
36
  const { data, isLoading, refetch, error } = useQuery<GetOrderAndTxsResponse>({
37
37
  queryKey: ["getAnyspendOrderAndTransactions", orderId],
38
- queryFn: () => anyspendService.getOrderAndTransactions(orderId!),
38
+ queryFn: () => anyspendService.getOrderAndTransactions(orderId),
39
39
  enabled: !!orderId,
40
40
  refetchInterval: 3000,
41
41
  staleTime: 1000,
@@ -6,7 +6,7 @@ import { useMemo } from "react";
6
6
  export function useCoinbaseOnrampOptions(country?: string, visitorData?: VisitorData, isLoadingVisitorData?: boolean) {
7
7
  const { data, isLoading, error, refetch } = useQuery({
8
8
  queryKey: ["getCoinbaseOnrampOptions", country, visitorData],
9
- queryFn: () => anyspendService.getCoinbaseOnrampOptions(country!, visitorData),
9
+ queryFn: () => anyspendService.getCoinbaseOnrampOptions(country, visitorData),
10
10
  enabled: Boolean(country) && !isLoadingVisitorData,
11
11
  });
12
12
 
@@ -111,6 +111,7 @@ export const anyspendService = {
111
111
  },
112
112
 
113
113
  getOrderAndTransactions: async (orderId: string | undefined): Promise<GetOrderAndTxsResponse> => {
114
+ invariant(orderId, "orderId is required");
114
115
  const response = await fetch(`${ANYSPEND_MAINNET_BASE_URL}/orders/${orderId}`);
115
116
  const data: GetOrderAndTxsResponse = await response.json();
116
117
  return data;
@@ -134,9 +135,10 @@ export const anyspendService = {
134
135
  },
135
136
 
136
137
  getCoinbaseOnrampOptions: async (
137
- country: string,
138
+ country: string | undefined,
138
139
  visitorData?: VisitorData,
139
140
  ): Promise<GetCoinbaseOnrampOptionsResponse> => {
141
+ invariant(country, "country is required");
140
142
  const params = new URLSearchParams({
141
143
  country,
142
144
  // include fingerprintId and requestId in the query params
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { loadScriptFromCDN } from "./utils/cdn-loader";
4
- import { formatNumberSmall } from "./utils/format";
5
3
  import { Loader2 } from "lucide-react";
6
4
  import { useEffect, useRef, useState } from "react";
7
5
  import { TradingViewProps } from "./types";
6
+ import { loadScriptFromCDN } from "./utils/cdn-loader";
7
+ import { formatNumberSmall } from "./utils/format";
8
8
 
9
9
  // TypeScript types - these will be loaded from CDN at runtime
10
10
  type ChartingLibraryWidgetOptions = any;
@@ -24,8 +24,6 @@ const GifLoadingOverlay = ({ className }: { className?: string }) => (
24
24
  );
25
25
 
26
26
  const TradingView = ({ className, tokenAddress, tokenSymbol }: TradingViewProps) => {
27
- const theme = "light";
28
-
29
27
  // Use token info for the current trade
30
28
  const currentTrade = {
31
29
  product_id: tokenAddress && tokenSymbol ? `${tokenSymbol}-${tokenAddress}` : "BONDKIT",
@@ -282,7 +280,7 @@ const TradingView = ({ className, tokenAddress, tokenSymbol }: TradingViewProps)
282
280
  tvWidgetRef.current = null;
283
281
  }
284
282
  };
285
- }, [theme, librariesLoaded]);
283
+ }, [librariesLoaded, currentTrade?.product_id, tradingViewDefaultInterval, tradingViewTimezone]);
286
284
 
287
285
  useEffect(() => {
288
286
  if (
@@ -1,15 +1,15 @@
1
- import type { Address, Hex, WalletClient, PublicClient } from "viem";
1
+ import type { Address, Hex, PublicClient, WalletClient } from "viem";
2
2
  import {
3
- parseUnits,
4
- formatUnits,
3
+ createPublicClient,
5
4
  encodeAbiParameters,
6
- parseAbiParameters,
5
+ formatUnits,
7
6
  getContract,
8
- createPublicClient,
9
7
  http,
8
+ parseAbiParameters,
9
+ parseUnits,
10
10
  } from "viem";
11
11
  import { base } from "viem/chains";
12
- import { UniversalRouterAddress, QuoterAddress, Permit2Address, BaseMainnetRpcUrl } from "./constants";
12
+ import { BaseMainnetRpcUrl, Permit2Address, QuoterAddress, UniversalRouterAddress } from "./constants";
13
13
  import type { SwapQuote } from "./types";
14
14
 
15
15
  // Minimal ABIs needed for swap functionality
@@ -230,7 +230,10 @@ export class BondkitSwapService {
230
230
  */
231
231
  private async getV4Config(): Promise<V4PoolConfig> {
232
232
  await this.initializeV4Config();
233
- return this.v4Config!;
233
+ if (!this.v4Config) {
234
+ throw new Error("Failed to initialize V4 configuration");
235
+ }
236
+ return this.v4Config;
234
237
  }
235
238
 
236
239
  /**
@@ -88,26 +88,30 @@ export function B3Provider({
88
88
  };
89
89
  }, [partnerId]);
90
90
 
91
+ // Stringify rpcUrls for stable comparison to prevent wagmiConfig recreation
92
+ // when parent component passes new object references with same content
93
+ const rpcUrlsString = useMemo(() => (rpcUrls ? JSON.stringify(rpcUrls) : undefined), [rpcUrls]);
94
+
91
95
  /**
92
96
  * Creates wagmi config with optional custom RPC URLs
93
97
  * @param rpcUrls - Optional mapping of chain IDs to RPC URLs
94
98
  */
95
- const wagmiConfig = useMemo(
96
- () =>
97
- createConfig({
98
- chains: [supportedChains[0], ...supportedChains.slice(1)],
99
- transports: Object.fromEntries(supportedChains.map(chain => [chain.id, http(rpcUrls?.[chain.id])])) as any,
100
- connectors: [
101
- inAppWalletConnector({
102
- ...(ecocystemConfig || {}),
103
- client,
104
- }),
105
- // injected(),
106
- // coinbaseWallet({ appName: "HypeDuel" }),
107
- ],
108
- }),
109
- [partnerId],
110
- );
99
+ const wagmiConfig = useMemo(() => {
100
+ const parsedRpcUrls = rpcUrlsString ? JSON.parse(rpcUrlsString) : undefined;
101
+
102
+ return createConfig({
103
+ chains: [supportedChains[0], ...supportedChains.slice(1)],
104
+ transports: Object.fromEntries(supportedChains.map(chain => [chain.id, http(parsedRpcUrls?.[chain.id])])),
105
+ connectors: [
106
+ inAppWalletConnector({
107
+ ...(ecocystemConfig || {}),
108
+ client,
109
+ }),
110
+ // injected(),
111
+ // coinbaseWallet({ appName: "HypeDuel" }),
112
+ ],
113
+ });
114
+ }, [ecocystemConfig, rpcUrlsString]);
111
115
 
112
116
  return (
113
117
  <ThirdwebProvider>
@@ -1,8 +1,9 @@
1
1
  import { fetchSimpleHashData } from "@b3dotfun/sdk/shared/utils/simplehash";
2
2
  import { useQuery } from "@tanstack/react-query";
3
+ import invariant from "invariant";
3
4
 
4
- async function fetchAccountAssets(address: string) {
5
- if (!address) throw new Error("Address is required");
5
+ async function fetchAccountAssets(address: string | undefined) {
6
+ invariant(address, "Address is required");
6
7
 
7
8
  const [nftResponse] = await Promise.all([
8
9
  fetchSimpleHashData(`/v0/nfts/owners`, {
@@ -27,7 +28,7 @@ async function fetchAccountAssets(address: string) {
27
28
  export function useAccountAssets(address?: string) {
28
29
  return useQuery({
29
30
  queryKey: ["accountAssets", address],
30
- queryFn: () => fetchAccountAssets(address!),
31
+ queryFn: () => fetchAccountAssets(address),
31
32
  enabled: Boolean(address),
32
33
  staleTime: 30 * 1000, // Consider data fresh for 30 seconds
33
34
  gcTime: 5 * 60 * 1000, // Keep unused data in cache for 5 minutes
@@ -100,7 +100,7 @@ export function useAuthentication(partnerId: string, loginWithSiwe?: boolean) {
100
100
  setIsAuthenticating(false);
101
101
  }
102
102
  useAutoConnectLoadingPrevious.current = useAutoConnectLoading;
103
- }, [useAutoConnectLoading]);
103
+ }, [useAutoConnectLoading, hasStartedConnecting, setIsAuthenticating]);
104
104
 
105
105
  // Ensure isAuthenticating stays true until we're fully ready
106
106
  useEffect(() => {
@@ -30,7 +30,7 @@ export const useOneBalance = (bypassCache = false) => {
30
30
 
31
31
  const { data, isLoading, refetch } = useQuery({
32
32
  queryKey: ["balances", address, `testnet-${sprinterTestnet}`],
33
- queryFn: () => fetchBalances(address!, sprinterTestnet),
33
+ queryFn: () => fetchBalances(address, sprinterTestnet),
34
34
  enabled: !!address,
35
35
  staleTime: 1000 * 60 * 1, // 1 minute
36
36
  gcTime: 1000 * 60 * 5, // 5 minutes (renamed from cacheTime in v5)
@@ -47,26 +47,33 @@ export function useQueryB3<
47
47
  const [error, setError] = useState<Error | null>(null);
48
48
  const [isLoading, setIsLoading] = useState<boolean>(false);
49
49
 
50
- const runQuery = useCallback(async (queryParams: ParamsType<T, M>) => {
51
- setIsLoading(true);
52
- try {
53
- // Cast the service to avoid TypeScript issues with dynamic services
54
- const serviceInstance = app.service(service) as any;
55
- const result = await serviceInstance[method](queryParams);
56
- setData(result); // Now `data` is correctly typed!
57
- return result;
58
- } catch (err) {
59
- setError(err instanceof Error ? err : new Error("An error occurred"));
60
- } finally {
61
- setIsLoading(false);
62
- }
63
- }, []);
50
+ const runQuery = useCallback(
51
+ async (queryParams: ParamsType<T, M>) => {
52
+ setIsLoading(true);
53
+ try {
54
+ // Cast the service to avoid TypeScript issues with dynamic services
55
+ const serviceInstance = app.service(service) as any;
56
+ const result = await serviceInstance[method](queryParams);
57
+ setData(result); // Now `data` is correctly typed!
58
+ return result;
59
+ } catch (err) {
60
+ setError(err instanceof Error ? err : new Error("An error occurred"));
61
+ } finally {
62
+ setIsLoading(false);
63
+ }
64
+ },
65
+ [service, method],
66
+ );
67
+
68
+ // Serialize params for stable comparison
69
+ const paramsJson = JSON.stringify(params);
64
70
 
65
71
  useEffect(() => {
66
72
  if (fetchInitially) {
67
73
  runQuery(params);
68
74
  }
69
- }, [runQuery, fetchInitially, JSON.stringify(params)]);
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ }, [runQuery, fetchInitially, paramsJson]);
70
77
 
71
78
  return { data, error, isLoading, runQuery };
72
79
  }
@@ -47,26 +47,33 @@ export function useQueryBSMNT<
47
47
  const [error, setError] = useState<Error | null>(null);
48
48
  const [isLoading, setIsLoading] = useState<boolean>(false);
49
49
 
50
- const runQuery = useCallback(async (queryParams: ParamsType<T, M>) => {
51
- setIsLoading(true);
52
- try {
53
- // Cast the service to avoid TypeScript issues with dynamic services
54
- const serviceInstance = app.service(service) as any;
55
- const result = await serviceInstance[method](queryParams);
56
- setData(result); // Now `data` is correctly typed!
57
- return result;
58
- } catch (err) {
59
- setError(err instanceof Error ? err : new Error("An error occurred"));
60
- } finally {
61
- setIsLoading(false);
62
- }
63
- }, []);
50
+ const runQuery = useCallback(
51
+ async (queryParams: ParamsType<T, M>) => {
52
+ setIsLoading(true);
53
+ try {
54
+ // Cast the service to avoid TypeScript issues with dynamic services
55
+ const serviceInstance = app.service(service) as any;
56
+ const result = await serviceInstance[method](queryParams);
57
+ setData(result); // Now `data` is correctly typed!
58
+ return result;
59
+ } catch (err) {
60
+ setError(err instanceof Error ? err : new Error("An error occurred"));
61
+ } finally {
62
+ setIsLoading(false);
63
+ }
64
+ },
65
+ [service, method],
66
+ );
67
+
68
+ // Serialize params for stable comparison
69
+ const paramsJson = JSON.stringify(params);
64
70
 
65
71
  useEffect(() => {
66
72
  if (fetchInitially) {
67
73
  runQuery(params);
68
74
  }
69
- }, [runQuery, fetchInitially, JSON.stringify(params)]);
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ }, [runQuery, fetchInitially, paramsJson]);
70
77
 
71
78
  return { data, error, isLoading, runQuery };
72
79
  }
@@ -7,6 +7,7 @@ import { viemToThirdwebChain } from "@b3dotfun/sdk/shared/constants/chains/b3Cha
7
7
  import { getChainById } from "@b3dotfun/sdk/shared/utils/chains";
8
8
  import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
9
9
  import { getWalletBalance } from "thirdweb/wallets";
10
+ import invariant from "invariant";
10
11
 
11
12
  type GetWalletBalanceResult = {
12
13
  value: bigint;
@@ -51,10 +52,12 @@ export function useTokenBalancesByChain({
51
52
  // Fetch native token balances
52
53
  Promise.all(
53
54
  chainIds.map(async chainId => {
55
+ const chain = getChainById(chainId);
56
+ invariant(chain, "Chain is required");
54
57
  const walletBalance = await getWalletBalance({
55
58
  address,
56
59
  client,
57
- chain: viemToThirdwebChain(getChainById(chainId)!),
60
+ chain: viemToThirdwebChain(chain),
58
61
  });
59
62
 
60
63
  return {
@@ -69,7 +69,7 @@ export function useTokenFromUrl({ defaultToken, prefix }: UseTokenFromUrlOptions
69
69
 
70
70
  const { data: tokenInfo, isError } = useQuery({
71
71
  queryKey: ["tokenInfo", network, currencyParam],
72
- queryFn: () => fetchTokenInfo(network, currencyParam!),
72
+ queryFn: () => fetchTokenInfo(network, currencyParam || ""),
73
73
  enabled: shouldFetchToken,
74
74
  staleTime: Infinity,
75
75
  gcTime: Infinity,
@@ -84,7 +84,7 @@ export function useTokenFromUrl({ defaultToken, prefix }: UseTokenFromUrlOptions
84
84
  if (isError || !tokenInfo) {
85
85
  return {
86
86
  ...defaultToken,
87
- address: currencyParam!,
87
+ address: currencyParam || "",
88
88
  chainId: Number(chainIdParam),
89
89
  };
90
90
  }
@@ -8,7 +8,7 @@ import { toast } from "sonner";
8
8
  import { prepareTransaction, sendTransaction as twSendTransaction } from "thirdweb";
9
9
  import { useActiveWallet } from "thirdweb/react";
10
10
  import { isAddress } from "viem";
11
- import { useSwitchChain, useWalletClient } from "wagmi";
11
+ import { useSwitchChain } from "wagmi";
12
12
  import { useB3 } from "../components";
13
13
  import { useAccountWallet } from "./useAccountWallet";
14
14
 
@@ -27,7 +27,6 @@ const partnerId = String(
27
27
  invariant(partnerId, "Partner ID is required");
28
28
 
29
29
  export function useUnifiedChainSwitchAndExecute() {
30
- const { data: walletClient } = useWalletClient();
31
30
  const { switchChainAsync } = useSwitchChain();
32
31
  const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = useState(false);
33
32
  const activeWallet = useActiveWallet();
@@ -122,7 +121,7 @@ export function useUnifiedChainSwitchAndExecute() {
122
121
  setIsSwitchingOrExecuting(false);
123
122
  }
124
123
  },
125
- [walletClient, switchChainAsync],
124
+ [connectedEOAWallet, activeWallet, switchChainAsync],
126
125
  );
127
126
 
128
127
  // Handle AA wallet transaction (no chain switch needed for AA)
@@ -19,7 +19,7 @@ export interface AssetBalance {
19
19
  chainBalances: ChainBalance[];
20
20
  }
21
21
 
22
- export const fetchBalances = async (address: string, testnet?: boolean): Promise<AssetBalance[]> => {
22
+ export const fetchBalances = async (address: string | undefined, testnet?: boolean): Promise<AssetBalance[]> => {
23
23
  if (!address) return [];
24
24
 
25
25
  const assetsResponse = await fetch(`${getSprinterBaseUrl(testnet)}/assets/fungible`);