@b3dotfun/sdk 0.0.58-alpha.1 → 0.0.58-alpha.3

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 (41) hide show
  1. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +4 -0
  2. package/dist/cjs/anyspend/utils/accountStore.d.ts +7 -0
  3. package/dist/cjs/anyspend/utils/accountStore.js +8 -0
  4. package/dist/cjs/anyspend/utils/index.d.ts +1 -0
  5. package/dist/cjs/anyspend/utils/index.js +1 -0
  6. package/dist/cjs/global-account/react/components/B3DynamicModal.js +17 -0
  7. package/dist/cjs/shared/react/components/CurrencySelector.js +8 -3
  8. package/dist/cjs/shared/react/components/FormattedCurrency.d.ts +3 -3
  9. package/dist/cjs/shared/react/components/FormattedCurrency.js +31 -26
  10. package/dist/cjs/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
  11. package/dist/cjs/shared/react/hooks/useCurrencyConversion.js +153 -94
  12. package/dist/cjs/shared/react/stores/currencyStore.d.ts +83 -8
  13. package/dist/cjs/shared/react/stores/currencyStore.js +147 -5
  14. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +5 -1
  15. package/dist/esm/anyspend/utils/accountStore.d.ts +7 -0
  16. package/dist/esm/anyspend/utils/accountStore.js +5 -0
  17. package/dist/esm/anyspend/utils/index.d.ts +1 -0
  18. package/dist/esm/anyspend/utils/index.js +1 -0
  19. package/dist/esm/global-account/react/components/B3DynamicModal.js +17 -0
  20. package/dist/esm/shared/react/components/CurrencySelector.js +10 -5
  21. package/dist/esm/shared/react/components/FormattedCurrency.d.ts +3 -3
  22. package/dist/esm/shared/react/components/FormattedCurrency.js +31 -26
  23. package/dist/esm/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
  24. package/dist/esm/shared/react/hooks/useCurrencyConversion.js +154 -95
  25. package/dist/esm/shared/react/stores/currencyStore.d.ts +83 -8
  26. package/dist/esm/shared/react/stores/currencyStore.js +143 -5
  27. package/dist/types/anyspend/utils/accountStore.d.ts +7 -0
  28. package/dist/types/anyspend/utils/index.d.ts +1 -0
  29. package/dist/types/shared/react/components/FormattedCurrency.d.ts +3 -3
  30. package/dist/types/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
  31. package/dist/types/shared/react/stores/currencyStore.d.ts +83 -8
  32. package/package.json +1 -1
  33. package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +6 -2
  34. package/src/anyspend/utils/accountStore.ts +12 -0
  35. package/src/anyspend/utils/index.ts +1 -0
  36. package/src/global-account/react/components/B3DynamicModal.tsx +20 -0
  37. package/src/shared/react/components/CurrencySelector.tsx +36 -5
  38. package/src/shared/react/components/FormattedCurrency.tsx +36 -30
  39. package/src/shared/react/hooks/__tests__/useCurrencyConversion.test.ts +14 -14
  40. package/src/shared/react/hooks/useCurrencyConversion.ts +163 -96
  41. package/src/shared/react/stores/currencyStore.ts +216 -10
@@ -16,20 +16,23 @@
16
16
  */
17
17
  export declare function useCurrencyConversion(): {
18
18
  /** Currently selected display currency */
19
- selectedCurrency: import("../stores/currencyStore").SupportedCurrency;
19
+ selectedCurrency: string;
20
20
  /** Base currency used for conversion (typically B3) */
21
- baseCurrency: import("../stores/currencyStore").SupportedCurrency;
21
+ baseCurrency: string;
22
22
  /** Current exchange rate from base to selected currency (undefined while loading) */
23
23
  exchangeRate: number | undefined;
24
24
  /** Format a value with currency conversion and proper symbol/decimal handling */
25
- formatCurrencyValue: (value: number, options?: {
25
+ formatCurrencyValue: (value: number, sourceCurrency: string, options?: {
26
26
  decimals?: number;
27
- currency?: string;
28
27
  }) => string;
29
28
  /** Format a tooltip value showing alternate currency representation */
30
- formatTooltipValue: (value: number, customCurrency?: string) => string;
29
+ formatTooltipValue: (value: number, sourceCurrency: string) => string;
31
30
  /** Symbol for the currently selected currency (e.g., "$", "€", "ETH") */
32
31
  selectedCurrencySymbol: string;
33
32
  /** Symbol for the base currency */
34
33
  baseCurrencySymbol: string;
34
+ /** Get exchange rate between any two currencies */
35
+ getExchangeRate: (from: string, to: string) => number | undefined;
36
+ /** All registered custom currencies */
37
+ customCurrencies: Record<string, import("../stores/currencyStore").CurrencyMetadata>;
35
38
  };
@@ -1,8 +1,36 @@
1
1
  /**
2
- * Supported currencies for display and conversion.
2
+ * Built-in supported currencies for display and conversion.
3
3
  * Includes fiat currencies (USD, EUR, GBP, JPY, CAD, AUD, KRW) and crypto (ETH, SOL, B3).
4
4
  */
5
5
  export type SupportedCurrency = "ETH" | "USD" | "EUR" | "GBP" | "JPY" | "CAD" | "AUD" | "B3" | "SOL" | "KRW";
6
+ /**
7
+ * Metadata for a custom currency including display formatting rules.
8
+ */
9
+ export interface CurrencyMetadata {
10
+ /** The currency code/symbol (e.g., "BTC", "DOGE") */
11
+ code: string;
12
+ /** Display symbol for the currency (e.g., "₿", "Ð") */
13
+ symbol: string;
14
+ /** Human-readable name (e.g., "Bitcoin", "Dogecoin") */
15
+ name: string;
16
+ /** Whether to show symbol before the value (true for $100, false for 100 ETH) */
17
+ prefixSymbol?: boolean;
18
+ /** Number of decimal places to show (undefined uses smart formatting) */
19
+ decimals?: number;
20
+ /** Whether to use subscript notation for small values */
21
+ showSubscripts?: boolean;
22
+ }
23
+ /**
24
+ * Exchange rate between two currencies.
25
+ */
26
+ export interface ExchangeRate {
27
+ /** Currency code being converted from */
28
+ from: string;
29
+ /** Currency code being converted to */
30
+ to: string;
31
+ /** Exchange rate multiplier (amount_in_to = amount_in_from * rate) */
32
+ rate: number;
33
+ }
6
34
  /**
7
35
  * Currency symbols used for display formatting.
8
36
  * Prefix currencies (USD, EUR, GBP, CAD, AUD) show symbol before the amount.
@@ -17,24 +45,51 @@ export declare const CURRENCY_NAMES: Record<SupportedCurrency, string>;
17
45
  * Currency store state interface.
18
46
  * @property selectedCurrency - The currency currently selected for display
19
47
  * @property baseCurrency - The base currency for conversion (typically B3)
48
+ * @property customCurrencies - Map of custom currency codes to their metadata
49
+ * @property customExchangeRates - Map of "FROM-TO" pairs to exchange rates
20
50
  * @property setSelectedCurrency - Update the selected display currency
21
51
  * @property setBaseCurrency - Update the base currency for conversions
52
+ * @property addCurrency - Register a new custom currency with metadata
53
+ * @property removeCurrency - Remove a custom currency
54
+ * @property setExchangeRate - Set a custom exchange rate between two currencies
55
+ * @property getExchangeRate - Get exchange rate between two currencies
56
+ * @property getAllCurrencies - Get all available currencies (built-in + custom)
22
57
  */
23
58
  interface CurrencyState {
24
- selectedCurrency: SupportedCurrency;
25
- baseCurrency: SupportedCurrency;
26
- setSelectedCurrency: (currency: SupportedCurrency) => void;
27
- setBaseCurrency: (currency: SupportedCurrency) => void;
59
+ selectedCurrency: string;
60
+ baseCurrency: string;
61
+ customCurrencies: Record<string, CurrencyMetadata>;
62
+ customExchangeRates: Record<string, number>;
63
+ setSelectedCurrency: (currency: string) => void;
64
+ setBaseCurrency: (currency: string) => void;
65
+ addCurrency: (metadata: CurrencyMetadata) => void;
66
+ removeCurrency: (code: string) => void;
67
+ setExchangeRate: (from: string, to: string, rate: number) => void;
68
+ getExchangeRate: (from: string, to: string) => number | undefined;
69
+ getAllCurrencies: () => string[];
28
70
  }
29
71
  /**
30
72
  * Zustand store for managing currency selection and conversion.
31
73
  * Persists user's selected currency preference in localStorage.
74
+ * Supports dynamic currency registration and custom exchange rates.
32
75
  *
33
76
  * @example
34
77
  * ```tsx
35
- * const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
36
- * // Change display currency to USD
37
- * setSelectedCurrency('USD');
78
+ * const { selectedCurrency, setSelectedCurrency, addCurrency, setExchangeRate } = useCurrencyStore();
79
+ *
80
+ * // Add a new currency
81
+ * addCurrency({
82
+ * code: "BTC",
83
+ * symbol: "₿",
84
+ * name: "Bitcoin",
85
+ * showSubscripts: true,
86
+ * });
87
+ *
88
+ * // Set exchange rate: 1 BTC = 50000 USD
89
+ * setExchangeRate("BTC", "USD", 50000);
90
+ *
91
+ * // Change display currency
92
+ * setSelectedCurrency('BTC');
38
93
  * ```
39
94
  */
40
95
  export declare const useCurrencyStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<CurrencyState>, "persist"> & {
@@ -48,4 +103,24 @@ export declare const useCurrencyStore: import("zustand").UseBoundStore<Omit<impo
48
103
  getOptions: () => Partial<import("zustand/middleware").PersistOptions<CurrencyState, CurrencyState>>;
49
104
  };
50
105
  }>;
106
+ /**
107
+ * Get the symbol for any currency (built-in or custom).
108
+ */
109
+ export declare function getCurrencySymbol(currency: string): string;
110
+ /**
111
+ * Get the name for any currency (built-in or custom).
112
+ */
113
+ export declare function getCurrencyName(currency: string): string;
114
+ /**
115
+ * Get metadata for a custom currency.
116
+ */
117
+ export declare function getCurrencyMetadata(currency: string): CurrencyMetadata | undefined;
118
+ /**
119
+ * Get the number of decimal places for a currency (for converting from smallest unit).
120
+ * Used when parsing amounts from wei/smallest unit format.
121
+ *
122
+ * @param currency - Currency code
123
+ * @returns Number of decimal places (e.g., 18 for ETH/wei, 2 for USD cents, 0 for JPY)
124
+ */
125
+ export declare function getCurrencyDecimalPlaces(currency: string): number;
51
126
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.58-alpha.1",
3
+ "version": "0.0.58-alpha.3",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useGlobalWalletState } from "@b3dotfun/sdk/anyspend/utils";
3
4
  import { useAccountWallet } from "@b3dotfun/sdk/global-account/react";
4
5
  import { cn } from "@b3dotfun/sdk/shared/utils/cn";
5
6
  import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
@@ -9,7 +10,7 @@ import { ChevronLeft, ChevronRightCircle, Wallet, X, ZapIcon } from "lucide-reac
9
10
  import { useState } from "react";
10
11
  import { createPortal } from "react-dom";
11
12
  import { toast } from "sonner";
12
- import { useSetActiveWallet, useWalletInfo } from "thirdweb/react";
13
+ import { useActiveWallet, useSetActiveWallet, useWalletInfo } from "thirdweb/react";
13
14
  import { WalletId, createWallet } from "thirdweb/wallets";
14
15
  import { useAccount, useConnect, useDisconnect, useWalletClient } from "wagmi";
15
16
 
@@ -51,10 +52,12 @@ export function CryptoPaymentMethod({
51
52
  const { disconnect } = useDisconnect();
52
53
  const { data: walletClient } = useWalletClient();
53
54
  const [showWalletModal, setShowWalletModal] = useState(false);
54
-
55
55
  const setActiveWallet = useSetActiveWallet();
56
56
  const { data: eoaWalletInfo } = useWalletInfo(connectedEOAWallet?.id);
57
57
 
58
+ const activeWallet = useActiveWallet();
59
+ const setGlobalAccountWallet = useGlobalWalletState(state => state.setGlobalAccountWallet);
60
+
58
61
  const isConnected = !!connectedEOAWallet;
59
62
  const globalAddress = connectedSmartWallet?.getAccount()?.address;
60
63
 
@@ -233,6 +236,7 @@ export function CryptoPaymentMethod({
233
236
  onClick={() => {
234
237
  setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
235
238
  onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
239
+ setGlobalAccountWallet(activeWallet);
236
240
  setActiveWallet(connectedEOAWallet);
237
241
  toast.success(`Selected ${eoaWalletInfo?.name || connector?.name || "wallet"}`);
238
242
  }}
@@ -0,0 +1,12 @@
1
+ import type { Wallet } from "thirdweb/wallets";
2
+ import { create } from "zustand";
3
+
4
+ interface GlobalWalletState {
5
+ globalAccountWallet?: Wallet;
6
+ setGlobalAccountWallet: (account?: Wallet) => void;
7
+ }
8
+
9
+ export const useGlobalWalletState = create<GlobalWalletState>(set => ({
10
+ globalAccountWallet: undefined,
11
+ setGlobalAccountWallet: account => set({ globalAccountWallet: account }),
12
+ }));
@@ -1,3 +1,4 @@
1
+ export * from "./accountStore";
1
2
  export * from "./address";
2
3
  export * from "./chain";
3
4
  export * from "./format";
@@ -10,9 +10,11 @@ import {
10
10
  } from "@b3dotfun/sdk/anyspend/react";
11
11
  import { AnySpendDepositHype } from "@b3dotfun/sdk/anyspend/react/components/AnyspendDepositHype";
12
12
  import { AnySpendStakeUpside } from "@b3dotfun/sdk/anyspend/react/components/AnySpendStakeUpside";
13
+ import { useGlobalWalletState } from "@b3dotfun/sdk/anyspend/utils";
13
14
  import { useIsMobile, useModalStore } from "@b3dotfun/sdk/global-account/react";
14
15
  import { cn } from "@b3dotfun/sdk/shared/utils/cn";
15
16
  import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
17
+ import { useEffect, useRef } from "react";
16
18
  import { AvatarEditor } from "./AvatarEditor/AvatarEditor";
17
19
  import { useB3 } from "./B3Provider/useB3";
18
20
  import { LinkAccount } from "./LinkAccount/LinkAccount";
@@ -21,6 +23,7 @@ import { RequestPermissions } from "./RequestPermissions/RequestPermissions";
21
23
  import { SignInWithB3Flow } from "./SignInWithB3/SignInWithB3Flow";
22
24
  import { Dialog, DialogContent, DialogDescription, DialogTitle } from "./ui/dialog";
23
25
  import { Drawer, DrawerContent, DrawerDescription, DrawerTitle } from "./ui/drawer";
26
+ import { useSetActiveWallet } from "thirdweb/react";
24
27
 
25
28
  const debug = debugB3React("B3DynamicModal");
26
29
 
@@ -28,6 +31,23 @@ export function B3DynamicModal() {
28
31
  const { isOpen, setB3ModalOpen, contentType, history, navigateBack } = useModalStore();
29
32
  const { theme } = useB3();
30
33
  const isMobile = useIsMobile();
34
+ const prevIsOpenRef = useRef(isOpen);
35
+
36
+ const globalAccountWallet = useGlobalWalletState(state => state.globalAccountWallet);
37
+ const setGlobalAccountWallet = useGlobalWalletState(state => state.setGlobalAccountWallet);
38
+ const setActiveWallet = useSetActiveWallet();
39
+
40
+ // anyspend cleanup global account chnages by setting account back
41
+ useEffect(() => {
42
+ if (prevIsOpenRef.current && !isOpen) {
43
+ if (globalAccountWallet) {
44
+ setActiveWallet(globalAccountWallet);
45
+ setGlobalAccountWallet(undefined);
46
+ }
47
+ }
48
+
49
+ prevIsOpenRef.current = isOpen;
50
+ }, [isOpen, globalAccountWallet, setActiveWallet, setGlobalAccountWallet]);
31
51
 
32
52
  // Define arrays for different modal type groups
33
53
  const fullWidthTypes = [
@@ -9,9 +9,16 @@ import {
9
9
  DropdownMenuSeparator,
10
10
  DropdownMenuTrigger,
11
11
  } from "../../../global-account/react/components/ui/dropdown-menu";
12
- import { CURRENCY_NAMES, CURRENCY_SYMBOLS, SupportedCurrency, useCurrencyStore } from "../stores/currencyStore";
12
+ import {
13
+ CURRENCY_NAMES,
14
+ CURRENCY_SYMBOLS,
15
+ SupportedCurrency,
16
+ useCurrencyStore,
17
+ getCurrencyName,
18
+ getCurrencySymbol,
19
+ } from "../stores/currencyStore";
13
20
 
14
- const currencies: SupportedCurrency[] = ["B3", "ETH", "SOL", "USD", "EUR", "GBP", "KRW", "JPY", "CAD", "AUD"];
21
+ const builtInCurrencies: SupportedCurrency[] = ["B3", "ETH", "SOL", "USD", "EUR", "GBP", "KRW", "JPY", "CAD", "AUD"];
15
22
 
16
23
  interface CurrencySelectorProps {
17
24
  labelClassName?: string;
@@ -20,7 +27,13 @@ interface CurrencySelectorProps {
20
27
  }
21
28
 
22
29
  export function CurrencySelector({ labelClassName, buttonVariant = "dark", label }: CurrencySelectorProps) {
23
- const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
30
+ const selectedCurrency = useCurrencyStore(state => state.selectedCurrency);
31
+ const setSelectedCurrency = useCurrencyStore(state => state.setSelectedCurrency);
32
+ const customCurrencies = useCurrencyStore(state => state.customCurrencies);
33
+
34
+ // Separate built-in and custom for better organization
35
+ const customCurrencyCodes = Object.keys(customCurrencies);
36
+ const hasCustomCurrencies = customCurrencyCodes.length > 0;
24
37
 
25
38
  return (
26
39
  <div className="flex items-center gap-2">
@@ -38,7 +51,7 @@ export function CurrencySelector({ labelClassName, buttonVariant = "dark", label
38
51
  </span>
39
52
  )}
40
53
  <Button variant={buttonVariant as any} className="flex items-center gap-2">
41
- <span className="text-sm font-medium">{CURRENCY_NAMES[selectedCurrency]}</span>
54
+ <span className="text-sm font-medium">{getCurrencyName(selectedCurrency)}</span>
42
55
  <svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
43
56
  <path
44
57
  fillRule="evenodd"
@@ -50,7 +63,7 @@ export function CurrencySelector({ labelClassName, buttonVariant = "dark", label
50
63
  </div>
51
64
  </DropdownMenuTrigger>
52
65
  <DropdownMenuContent align="end" className="z-[100] min-w-[200px]">
53
- {currencies.map(currency => (
66
+ {builtInCurrencies.map(currency => (
54
67
  <div key={currency}>
55
68
  <DropdownMenuItem
56
69
  onClick={() => setSelectedCurrency(currency)}
@@ -64,6 +77,24 @@ export function CurrencySelector({ labelClassName, buttonVariant = "dark", label
64
77
  {currency === "SOL" && <DropdownMenuSeparator key="separator" className="bg-border my-1" />}
65
78
  </div>
66
79
  ))}
80
+
81
+ {hasCustomCurrencies && (
82
+ <>
83
+ <DropdownMenuSeparator className="bg-border my-1" />
84
+ {customCurrencyCodes.map(currency => (
85
+ <DropdownMenuItem
86
+ key={currency}
87
+ onClick={() => setSelectedCurrency(currency)}
88
+ className={`flex cursor-pointer items-center justify-between gap-3 px-3 py-2.5 transition-colors ${
89
+ selectedCurrency === currency ? "bg-accent" : "hover:bg-accent/50"
90
+ }`}
91
+ >
92
+ <span className="text-foreground text-sm font-medium">{getCurrencyName(currency)}</span>
93
+ <span className="text-muted-foreground text-xs font-medium">{getCurrencySymbol(currency)}</span>
94
+ </DropdownMenuItem>
95
+ ))}
96
+ </>
97
+ )}
67
98
  </DropdownMenuContent>
68
99
  </DropdownMenu>
69
100
  </div>
@@ -5,47 +5,56 @@ import { cn } from "@b3dotfun/sdk/shared/utils";
5
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../../global-account/react/components/ui/tooltip";
6
6
  import { useCurrencyConversion } from "../hooks/useCurrencyConversion";
7
7
  import { useCurrencyModalStore } from "../stores/currencyModalStore";
8
+ import { getCurrencyDecimalPlaces } from "../stores/currencyStore";
8
9
 
9
10
  interface FormattedCurrencyProps {
10
- amount: number;
11
+ amount: string; // Wei amount as string (will be divided by 1e18)
12
+ sourceCurrency: string; // The currency the amount is in (e.g., "WIN", "B3", "USD")
11
13
  showChange?: boolean;
12
14
  showColor?: boolean;
13
15
  className?: string;
14
16
  subB3Icon?: boolean;
15
17
  clickable?: boolean;
16
18
  decimals?: number;
17
- currency?: string; // Override currency (e.g., "ETH", "USDC", "B3")
18
19
  }
19
20
 
20
21
  export function FormattedCurrency({
21
22
  amount,
23
+ sourceCurrency,
22
24
  showChange = false,
23
25
  showColor = false,
24
26
  className,
25
27
  subB3Icon = true,
26
28
  clickable = true,
27
29
  decimals,
28
- currency,
29
30
  }: FormattedCurrencyProps) {
30
31
  const { formatCurrencyValue, formatTooltipValue, selectedCurrency, baseCurrency } = useCurrencyConversion();
31
32
  const { openModal } = useCurrencyModalStore();
32
33
 
33
- // Use passed currency or fall back to selected currency
34
- const activeCurrency = currency || selectedCurrency;
34
+ // Convert from smallest unit to human-readable using currency's decimal places
35
+ const decimalPlaces = getCurrencyDecimalPlaces(sourceCurrency);
36
+ const divisor = Math.pow(10, decimalPlaces);
35
37
 
36
- const isPositive = amount >= 0;
38
+ // Parse amount - handle both string and numeric inputs, including negatives
39
+ let parsedAmount: number;
40
+ if (typeof amount === "string") {
41
+ // Handle BigInt strings and negative values
42
+ const numericAmount = amount.startsWith("-") ? -Math.abs(parseFloat(amount.replace("-", ""))) : parseFloat(amount);
43
+ parsedAmount = numericAmount / divisor;
44
+ } else {
45
+ parsedAmount = amount / divisor;
46
+ }
47
+
48
+ const isPositive = parsedAmount >= 0;
37
49
 
38
- // Get the formatted value (using absolute value for negative numbers when showing change)
39
- const baseAmount = showChange ? Math.abs(amount) : amount;
50
+ // Always format with absolute value, we'll add the sign separately
51
+ const absoluteAmount = Math.abs(parsedAmount);
40
52
 
41
- // Use centralized formatting from hook with optional overrides
42
- const formattedValue = formatCurrencyValue(baseAmount, {
43
- decimals,
44
- currency,
45
- });
53
+ // Format value with automatic conversion from source to display currency
54
+ const formattedValue = formatCurrencyValue(absoluteAmount, sourceCurrency, { decimals });
46
55
 
47
56
  // Generate tooltip using the centralized hook function
48
- const baseTooltipValue = formatTooltipValue(amount, currency);
57
+ const baseTooltipValue = formatTooltipValue(parsedAmount, sourceCurrency);
49
58
 
50
59
  // Add change indicator if needed
51
60
  const tooltipValue = showChange ? `${isPositive ? "+" : "-"}${baseTooltipValue}` : baseTooltipValue;
@@ -60,14 +69,14 @@ export function FormattedCurrency({
60
69
  }
61
70
  }
62
71
 
63
- // Add change indicator
72
+ // Build display value with appropriate sign
64
73
  let displayValue = formattedValue;
65
74
  if (showChange) {
66
- if (isPositive) {
67
- displayValue = `+${formattedValue}`;
68
- } else {
69
- displayValue = `-${formattedValue}`;
70
- }
75
+ // Add +/- prefix for change indicators
76
+ displayValue = `${isPositive ? "+" : "-"}${formattedValue}`;
77
+ } else if (!isPositive) {
78
+ // Add minus sign for negative values
79
+ displayValue = `-${formattedValue}`;
71
80
  }
72
81
 
73
82
  const handleClick = () => {
@@ -76,6 +85,9 @@ export function FormattedCurrency({
76
85
  }
77
86
  };
78
87
 
88
+ // Check if we should show B3 icon (when displaying in B3 and baseCurrency is B3)
89
+ const shouldShowB3Icon = subB3Icon && selectedCurrency === "B3" && baseCurrency === "B3";
90
+
79
91
  return (
80
92
  <Tooltip>
81
93
  <TooltipTrigger asChild>
@@ -88,16 +100,10 @@ export function FormattedCurrency({
88
100
  clickable && "cursor-pointer transition-opacity hover:opacity-80",
89
101
  )}
90
102
  >
91
- {subB3Icon &&
92
- (currency === baseCurrency || (!currency && activeCurrency === baseCurrency)) &&
93
- baseCurrency === "B3"
94
- ? displayValue.split(" ")[0]
95
- : displayValue}
96
- {subB3Icon &&
97
- (currency === baseCurrency || (!currency && activeCurrency === baseCurrency)) &&
98
- baseCurrency === "B3" && (
99
- <img src={B3_COIN_IMAGE_URL} className="inline-block h-4 w-4 align-middle" alt="B3 coin" />
100
- )}
103
+ {shouldShowB3Icon ? displayValue.split(" ")[0] : displayValue}
104
+ {shouldShowB3Icon && (
105
+ <img src={B3_COIN_IMAGE_URL} className="inline-block h-4 w-4 align-middle" alt="B3 coin" />
106
+ )}
101
107
  </span>
102
108
  </TooltipTrigger>
103
109
  <TooltipContent>{tooltipValue}</TooltipContent>
@@ -67,7 +67,7 @@ describe("useCurrencyConversion", () => {
67
67
  mockStoreState.baseCurrency = "B3";
68
68
 
69
69
  const { result } = renderHook(() => useCurrencyConversion());
70
- const formatted = result.current.formatCurrencyValue(100);
70
+ const formatted = result.current.formatCurrencyValue(100, "B3");
71
71
 
72
72
  expect(formatted).toContain("B3");
73
73
  expect(formatted).toContain("100");
@@ -79,7 +79,7 @@ describe("useCurrencyConversion", () => {
79
79
  mockStoreState.baseCurrency = "B3";
80
80
 
81
81
  const { result } = renderHook(() => useCurrencyConversion());
82
- const formatted = result.current.formatCurrencyValue(100);
82
+ const formatted = result.current.formatCurrencyValue(100, "B3");
83
83
 
84
84
  expect(formatted).toContain("B3");
85
85
  expect(formatted).not.toContain("$");
@@ -91,7 +91,7 @@ describe("useCurrencyConversion", () => {
91
91
  mockStoreState.baseCurrency = "B3";
92
92
 
93
93
  const { result } = renderHook(() => useCurrencyConversion());
94
- const formatted = result.current.formatCurrencyValue(100);
94
+ const formatted = result.current.formatCurrencyValue(100, "B3");
95
95
 
96
96
  expect(formatted).toMatch(/^\$/);
97
97
  expect(formatted).toContain("200");
@@ -104,7 +104,7 @@ describe("useCurrencyConversion", () => {
104
104
  mockStoreState.baseCurrency = "B3";
105
105
 
106
106
  const { result } = renderHook(() => useCurrencyConversion());
107
- const formatted = result.current.formatCurrencyValue(100);
107
+ const formatted = result.current.formatCurrencyValue(100, "B3");
108
108
 
109
109
  expect(formatted).toMatch(/^€/);
110
110
  });
@@ -116,7 +116,7 @@ describe("useCurrencyConversion", () => {
116
116
  mockStoreState.baseCurrency = "B3";
117
117
 
118
118
  const { result } = renderHook(() => useCurrencyConversion());
119
- const formatted = result.current.formatCurrencyValue(100);
119
+ const formatted = result.current.formatCurrencyValue(100, "B3");
120
120
 
121
121
  expect(formatted).toContain("¥");
122
122
  expect(formatted).not.toContain(".");
@@ -129,7 +129,7 @@ describe("useCurrencyConversion", () => {
129
129
  mockStoreState.baseCurrency = "B3";
130
130
 
131
131
  const { result } = renderHook(() => useCurrencyConversion());
132
- const formatted = result.current.formatCurrencyValue(100);
132
+ const formatted = result.current.formatCurrencyValue(100, "B3");
133
133
 
134
134
  expect(formatted).toContain("₩");
135
135
  expect(formatted).not.toContain(".");
@@ -142,7 +142,7 @@ describe("useCurrencyConversion", () => {
142
142
  mockStoreState.baseCurrency = "B3";
143
143
 
144
144
  const { result } = renderHook(() => useCurrencyConversion());
145
- const formatted = result.current.formatCurrencyValue(100);
145
+ const formatted = result.current.formatCurrencyValue(100, "B3");
146
146
 
147
147
  expect(formatted).toContain("ETH");
148
148
  expect(formatted).not.toMatch(/^ETH/);
@@ -155,7 +155,7 @@ describe("useCurrencyConversion", () => {
155
155
  mockStoreState.baseCurrency = "B3";
156
156
 
157
157
  const { result } = renderHook(() => useCurrencyConversion());
158
- const formatted = result.current.formatCurrencyValue(100);
158
+ const formatted = result.current.formatCurrencyValue(100, "B3");
159
159
 
160
160
  expect(formatted).toContain("SOL");
161
161
  expect(formatted).not.toMatch(/^SOL/);
@@ -167,7 +167,7 @@ describe("useCurrencyConversion", () => {
167
167
  mockStoreState.baseCurrency = "B3";
168
168
 
169
169
  const { result } = renderHook(() => useCurrencyConversion());
170
- const formatted = result.current.formatCurrencyValue(10);
170
+ const formatted = result.current.formatCurrencyValue(10, "B3");
171
171
 
172
172
  // 10 * 1.5 = 15
173
173
  expect(formatted).toMatch(/^\$/);
@@ -182,7 +182,7 @@ describe("useCurrencyConversion", () => {
182
182
 
183
183
  const { result } = renderHook(() => useCurrencyConversion());
184
184
  const inputValue = 100;
185
- const formatted = result.current.formatCurrencyValue(inputValue);
185
+ const formatted = result.current.formatCurrencyValue(inputValue, "B3");
186
186
 
187
187
  expect(formatted).toContain("350");
188
188
  });
@@ -236,7 +236,7 @@ describe("useCurrencyConversion", () => {
236
236
  mockStoreState.baseCurrency = "B3";
237
237
 
238
238
  const { result } = renderHook(() => useCurrencyConversion());
239
- const tooltip = result.current.formatTooltipValue(100);
239
+ const tooltip = result.current.formatTooltipValue(100, "B3");
240
240
 
241
241
  expect(tooltip).toContain("USD");
242
242
  expect(tooltip).toContain("150");
@@ -249,7 +249,7 @@ describe("useCurrencyConversion", () => {
249
249
  mockStoreState.baseCurrency = "B3";
250
250
 
251
251
  const { result } = renderHook(() => useCurrencyConversion());
252
- const tooltip = result.current.formatTooltipValue(100);
252
+ const tooltip = result.current.formatTooltipValue(100, "B3");
253
253
 
254
254
  expect(tooltip).toContain("B3");
255
255
  expect(tooltip).toContain("100");
@@ -286,7 +286,7 @@ describe("useCurrencyConversion", () => {
286
286
  mockStoreState.baseCurrency = "B3";
287
287
 
288
288
  const { result } = renderHook(() => useCurrencyConversion());
289
- const tooltip = result.current.formatTooltipValue(-100);
289
+ const tooltip = result.current.formatTooltipValue(-100, "B3");
290
290
 
291
291
  expect(tooltip).toContain("USD");
292
292
  expect(tooltip).toContain("150");
@@ -299,7 +299,7 @@ describe("useCurrencyConversion", () => {
299
299
  mockStoreState.baseCurrency = "B3";
300
300
 
301
301
  const { result } = renderHook(() => useCurrencyConversion());
302
- const tooltip = result.current.formatTooltipValue(100);
302
+ const tooltip = result.current.formatTooltipValue(100, "B3");
303
303
 
304
304
  expect(tooltip).toContain("USD");
305
305
  expect(tooltip).toContain("100");