@b3dotfun/sdk 0.0.42 → 0.0.43-alpha.1

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 (121) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +1 -0
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +11 -4
  3. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +43 -3
  4. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.d.ts +1 -0
  5. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +2 -2
  6. package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  7. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +5 -3
  8. package/dist/cjs/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +12 -0
  9. package/dist/cjs/anyspend/types/api.d.ts +2 -10
  10. package/dist/cjs/global-account/react/components/SignInWithB3/SignIn.js +1 -1
  11. package/dist/cjs/global-account/react/components/index.d.ts +8 -7
  12. package/dist/cjs/global-account/react/components/index.js +29 -23
  13. package/dist/cjs/global-account/react/components/ui/dropdown-menu.d.ts +27 -0
  14. package/dist/cjs/global-account/react/components/ui/dropdown-menu.js +100 -0
  15. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +5 -1
  16. package/dist/cjs/shared/constants/currency.d.ts +1 -0
  17. package/dist/cjs/shared/constants/currency.js +5 -0
  18. package/dist/cjs/shared/constants/index.d.ts +1 -0
  19. package/dist/cjs/shared/constants/index.js +15 -0
  20. package/dist/cjs/shared/react/components/CurrencySelector.d.ts +7 -0
  21. package/dist/cjs/shared/react/components/CurrencySelector.js +14 -0
  22. package/dist/cjs/shared/react/components/FormattedCurrency.d.ts +12 -0
  23. package/dist/cjs/shared/react/components/FormattedCurrency.js +60 -0
  24. package/dist/cjs/shared/react/components/index.d.ts +2 -0
  25. package/dist/cjs/shared/react/components/index.js +18 -0
  26. package/dist/cjs/shared/react/hooks/__tests__/useCurrencyConversion.test.d.ts +1 -0
  27. package/dist/cjs/shared/react/hooks/__tests__/useCurrencyConversion.test.js +245 -0
  28. package/dist/cjs/shared/react/hooks/index.d.ts +1 -0
  29. package/dist/cjs/shared/react/hooks/index.js +1 -0
  30. package/dist/cjs/shared/react/hooks/useCurrencyConversion.d.ts +35 -0
  31. package/dist/cjs/shared/react/hooks/useCurrencyConversion.js +200 -0
  32. package/dist/cjs/shared/react/index.d.ts +2 -0
  33. package/dist/cjs/shared/react/index.js +2 -0
  34. package/dist/cjs/shared/react/stores/currencyModalStore.d.ts +7 -0
  35. package/dist/cjs/shared/react/stores/currencyModalStore.js +9 -0
  36. package/dist/cjs/shared/react/stores/currencyStore.d.ts +51 -0
  37. package/dist/cjs/shared/react/stores/currencyStore.js +57 -0
  38. package/dist/cjs/shared/react/stores/index.d.ts +2 -0
  39. package/dist/cjs/shared/react/stores/index.js +18 -0
  40. package/dist/esm/anyspend/react/components/AnySpend.d.ts +1 -0
  41. package/dist/esm/anyspend/react/components/AnySpend.js +11 -4
  42. package/dist/esm/anyspend/react/components/AnySpendCustom.js +10 -3
  43. package/dist/esm/anyspend/react/components/AnyspendDepositHype.d.ts +1 -0
  44. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +2 -2
  45. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  46. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +5 -3
  47. package/dist/esm/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +12 -0
  48. package/dist/esm/anyspend/types/api.d.ts +2 -10
  49. package/dist/esm/global-account/react/components/SignInWithB3/SignIn.js +1 -1
  50. package/dist/esm/global-account/react/components/index.d.ts +8 -7
  51. package/dist/esm/global-account/react/components/index.js +8 -7
  52. package/dist/esm/global-account/react/components/ui/dropdown-menu.d.ts +27 -0
  53. package/dist/esm/global-account/react/components/ui/dropdown-menu.js +60 -0
  54. package/dist/esm/global-account/react/stores/useModalStore.d.ts +5 -1
  55. package/dist/esm/shared/constants/currency.d.ts +1 -0
  56. package/dist/esm/shared/constants/currency.js +2 -0
  57. package/dist/esm/shared/constants/index.d.ts +1 -0
  58. package/dist/esm/shared/constants/index.js +1 -0
  59. package/dist/esm/shared/react/components/CurrencySelector.d.ts +7 -0
  60. package/dist/esm/shared/react/components/CurrencySelector.js +11 -0
  61. package/dist/esm/shared/react/components/FormattedCurrency.d.ts +12 -0
  62. package/dist/esm/shared/react/components/FormattedCurrency.js +57 -0
  63. package/dist/esm/shared/react/components/index.d.ts +2 -0
  64. package/dist/esm/shared/react/components/index.js +2 -0
  65. package/dist/esm/shared/react/hooks/__tests__/useCurrencyConversion.test.d.ts +1 -0
  66. package/dist/esm/shared/react/hooks/__tests__/useCurrencyConversion.test.js +243 -0
  67. package/dist/esm/shared/react/hooks/index.d.ts +1 -0
  68. package/dist/esm/shared/react/hooks/index.js +1 -0
  69. package/dist/esm/shared/react/hooks/useCurrencyConversion.d.ts +35 -0
  70. package/dist/esm/shared/react/hooks/useCurrencyConversion.js +197 -0
  71. package/dist/esm/shared/react/index.d.ts +2 -0
  72. package/dist/esm/shared/react/index.js +2 -0
  73. package/dist/esm/shared/react/stores/currencyModalStore.d.ts +7 -0
  74. package/dist/esm/shared/react/stores/currencyModalStore.js +6 -0
  75. package/dist/esm/shared/react/stores/currencyStore.d.ts +51 -0
  76. package/dist/esm/shared/react/stores/currencyStore.js +54 -0
  77. package/dist/esm/shared/react/stores/index.d.ts +2 -0
  78. package/dist/esm/shared/react/stores/index.js +2 -0
  79. package/dist/styles/index.css +1 -1
  80. package/dist/types/anyspend/react/components/AnySpend.d.ts +1 -0
  81. package/dist/types/anyspend/react/components/AnyspendDepositHype.d.ts +1 -0
  82. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  83. package/dist/types/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +12 -0
  84. package/dist/types/anyspend/types/api.d.ts +2 -10
  85. package/dist/types/global-account/react/components/index.d.ts +8 -7
  86. package/dist/types/global-account/react/components/ui/dropdown-menu.d.ts +27 -0
  87. package/dist/types/global-account/react/stores/useModalStore.d.ts +5 -1
  88. package/dist/types/shared/constants/currency.d.ts +1 -0
  89. package/dist/types/shared/constants/index.d.ts +1 -0
  90. package/dist/types/shared/react/components/CurrencySelector.d.ts +7 -0
  91. package/dist/types/shared/react/components/FormattedCurrency.d.ts +12 -0
  92. package/dist/types/shared/react/components/index.d.ts +2 -0
  93. package/dist/types/shared/react/hooks/__tests__/useCurrencyConversion.test.d.ts +1 -0
  94. package/dist/types/shared/react/hooks/index.d.ts +1 -0
  95. package/dist/types/shared/react/hooks/useCurrencyConversion.d.ts +35 -0
  96. package/dist/types/shared/react/index.d.ts +2 -0
  97. package/dist/types/shared/react/stores/currencyModalStore.d.ts +7 -0
  98. package/dist/types/shared/react/stores/currencyStore.d.ts +51 -0
  99. package/dist/types/shared/react/stores/index.d.ts +2 -0
  100. package/package.json +29 -3
  101. package/src/anyspend/react/components/AnySpend.tsx +15 -2
  102. package/src/anyspend/react/components/AnySpendCustom.tsx +11 -2
  103. package/src/anyspend/react/components/AnyspendDepositHype.tsx +3 -0
  104. package/src/anyspend/react/components/common/PanelOnramp.tsx +19 -15
  105. package/src/anyspend/types/api.ts +2 -10
  106. package/src/global-account/react/components/SignInWithB3/SignIn.tsx +2 -7
  107. package/src/global-account/react/components/index.ts +19 -12
  108. package/src/global-account/react/components/ui/dropdown-menu.tsx +132 -0
  109. package/src/global-account/react/stores/useModalStore.ts +5 -1
  110. package/src/shared/constants/currency.ts +2 -0
  111. package/src/shared/constants/index.ts +2 -0
  112. package/src/shared/react/components/CurrencySelector.tsx +71 -0
  113. package/src/shared/react/components/FormattedCurrency.tsx +106 -0
  114. package/src/shared/react/components/index.ts +2 -0
  115. package/src/shared/react/hooks/__tests__/useCurrencyConversion.test.ts +308 -0
  116. package/src/shared/react/hooks/index.ts +1 -0
  117. package/src/shared/react/hooks/useCurrencyConversion.ts +211 -0
  118. package/src/shared/react/index.ts +2 -0
  119. package/src/shared/react/stores/currencyModalStore.ts +13 -0
  120. package/src/shared/react/stores/currencyStore.ts +82 -0
  121. package/src/shared/react/stores/index.ts +2 -0
@@ -0,0 +1,132 @@
1
+ "use client";
2
+
3
+ import { cn } from "@b3dotfun/sdk/shared/utils";
4
+ import * as React from "react";
5
+
6
+ interface DropdownMenuProps {
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ interface DropdownMenuContentProps {
11
+ children: React.ReactNode;
12
+ align?: "start" | "center" | "end";
13
+ className?: string;
14
+ }
15
+
16
+ interface DropdownMenuItemProps {
17
+ children: React.ReactNode;
18
+ onClick?: () => void;
19
+ className?: string;
20
+ }
21
+
22
+ interface DropdownMenuSeparatorProps {
23
+ className?: string;
24
+ }
25
+
26
+ interface DropdownMenuTriggerProps {
27
+ children: React.ReactNode;
28
+ asChild?: boolean;
29
+ }
30
+
31
+ const DropdownMenuContext = React.createContext<{
32
+ isOpen: boolean;
33
+ setIsOpen: (open: boolean) => void;
34
+ }>({
35
+ isOpen: false,
36
+ setIsOpen: () => {},
37
+ });
38
+
39
+ export function DropdownMenu({ children }: DropdownMenuProps) {
40
+ const [isOpen, setIsOpen] = React.useState(false);
41
+
42
+ React.useEffect(() => {
43
+ const handleClickOutside = (event: MouseEvent) => {
44
+ const target = event.target as Element;
45
+ if (!target.closest("[data-dropdown-menu]")) {
46
+ setIsOpen(false);
47
+ }
48
+ };
49
+
50
+ if (isOpen) {
51
+ document.addEventListener("click", handleClickOutside);
52
+ return () => document.removeEventListener("click", handleClickOutside);
53
+ }
54
+ }, [isOpen]);
55
+
56
+ return (
57
+ <DropdownMenuContext.Provider value={{ isOpen, setIsOpen }}>
58
+ <div className="relative" data-dropdown-menu>
59
+ {children}
60
+ </div>
61
+ </DropdownMenuContext.Provider>
62
+ );
63
+ }
64
+
65
+ export function DropdownMenuTrigger({ children, asChild }: DropdownMenuTriggerProps) {
66
+ const { isOpen, setIsOpen } = React.useContext(DropdownMenuContext);
67
+
68
+ const handleClick = () => {
69
+ setIsOpen(!isOpen);
70
+ };
71
+
72
+ if (asChild) {
73
+ if (React.isValidElement(children)) {
74
+ return React.cloneElement(children as React.ReactElement<{ onClick?: () => void }>, {
75
+ onClick: handleClick,
76
+ });
77
+ }
78
+ }
79
+
80
+ return <button onClick={handleClick}>{children}</button>;
81
+ }
82
+
83
+ export function DropdownMenuContent({ children, align = "start", className }: DropdownMenuContentProps) {
84
+ const { isOpen } = React.useContext(DropdownMenuContext);
85
+
86
+ if (!isOpen) return null;
87
+
88
+ const alignmentClasses = {
89
+ start: "left-0",
90
+ center: "left-1/2 -translate-x-1/2",
91
+ end: "right-0",
92
+ };
93
+
94
+ return (
95
+ <div
96
+ className={cn(
97
+ "bg-popover text-popover-foreground absolute top-full z-50 mt-1 min-w-32 overflow-hidden rounded-md border p-1 shadow-md",
98
+ "border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800",
99
+ alignmentClasses[align],
100
+ className,
101
+ )}
102
+ >
103
+ {children}
104
+ </div>
105
+ );
106
+ }
107
+
108
+ export function DropdownMenuItem({ children, onClick, className }: DropdownMenuItemProps) {
109
+ const { setIsOpen } = React.useContext(DropdownMenuContext);
110
+
111
+ const handleClick = () => {
112
+ onClick?.();
113
+ setIsOpen(false);
114
+ };
115
+
116
+ return (
117
+ <div
118
+ className={cn(
119
+ "hover:bg-accent hover:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
120
+ "hover:bg-gray-100 dark:hover:bg-gray-700",
121
+ className,
122
+ )}
123
+ onClick={handleClick}
124
+ >
125
+ {children}
126
+ </div>
127
+ );
128
+ }
129
+
130
+ export function DropdownMenuSeparator({ className }: DropdownMenuSeparatorProps) {
131
+ return <div className={cn("bg-muted -mx-1 my-1 h-px", "bg-gray-200 dark:bg-gray-600", className)} />;
132
+ }
@@ -123,11 +123,13 @@ export interface AnySpendModalProps extends BaseModalProps {
123
123
  /** Whether to hide the transaction history button */
124
124
  hideTransactionHistoryButton?: boolean;
125
125
  /** Callback function called when the transaction is successful */
126
- onSuccess?: () => void;
126
+ onSuccess?: (txHash?: string) => void;
127
127
  /** Token address of the destination token to buy (enables buy mode) */
128
128
  destinationTokenAddress?: string;
129
129
  /** Chain ID where the destination token exists (enables buy mode) */
130
130
  destinationTokenChainId?: number;
131
+ /** Custom USD input values for quick amount buttons in fiat onramp */
132
+ customUsdInputValues?: string[];
131
133
  }
132
134
 
133
135
  /**
@@ -307,6 +309,8 @@ export interface AnySpendDepositHypeProps extends BaseModalProps {
307
309
  mainFooter?: React.ReactNode;
308
310
  /** Callback function called when the deposit is successful */
309
311
  onSuccess?: (amount?: string) => void;
312
+ /** Custom USD input values for quick amount buttons in fiat onramp */
313
+ customUsdInputValues?: string[];
310
314
  }
311
315
 
312
316
  export interface AvatarEditorModalProps extends BaseModalProps {
@@ -0,0 +1,2 @@
1
+ // B3 Token image URL
2
+ export const B3_COIN_IMAGE_URL = "https://cdn.b3.fun/b3-coin-3d.png";
@@ -1,3 +1,5 @@
1
+ export * from "./currency";
2
+
1
3
  export const siteURL = "https://basement.fun";
2
4
 
3
5
  export const b3CoinIcon = "https://cdn.b3.fun/b3-coin-3d.png";
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { cn } from "@b3dotfun/sdk/shared/utils";
4
+ import { Button } from "../../../global-account/react/components/ui/button";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuTrigger,
11
+ } from "../../../global-account/react/components/ui/dropdown-menu";
12
+ import { CURRENCY_NAMES, CURRENCY_SYMBOLS, SupportedCurrency, useCurrencyStore } from "../stores/currencyStore";
13
+
14
+ const currencies: SupportedCurrency[] = ["B3", "ETH", "SOL", "USD", "EUR", "GBP", "KRW", "JPY", "CAD", "AUD"];
15
+
16
+ interface CurrencySelectorProps {
17
+ labelClassName?: string;
18
+ buttonVariant?: "dark" | "primary" | "ghost" | "gold";
19
+ label?: string;
20
+ }
21
+
22
+ export function CurrencySelector({ labelClassName, buttonVariant = "dark", label }: CurrencySelectorProps) {
23
+ const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
24
+
25
+ return (
26
+ <div className="flex items-center gap-2">
27
+ <DropdownMenu>
28
+ <DropdownMenuTrigger asChild>
29
+ <div className="flex items-center gap-3">
30
+ {label && (
31
+ <span
32
+ className={cn(
33
+ "text-foreground text-sm font-medium leading-none tracking-tight sm:text-base",
34
+ labelClassName,
35
+ )}
36
+ >
37
+ {label}
38
+ </span>
39
+ )}
40
+ <Button variant={buttonVariant as any} className="flex items-center gap-2">
41
+ <span className="text-sm font-medium">{CURRENCY_NAMES[selectedCurrency]}</span>
42
+ <svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
43
+ <path
44
+ fillRule="evenodd"
45
+ d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
46
+ clipRule="evenodd"
47
+ />
48
+ </svg>
49
+ </Button>
50
+ </div>
51
+ </DropdownMenuTrigger>
52
+ <DropdownMenuContent align="end" className="z-[100] min-w-[200px]">
53
+ {currencies.map(currency => (
54
+ <div key={currency}>
55
+ <DropdownMenuItem
56
+ onClick={() => setSelectedCurrency(currency)}
57
+ className={`flex cursor-pointer items-center justify-between gap-3 px-3 py-2.5 transition-colors ${
58
+ selectedCurrency === currency ? "bg-accent" : "hover:bg-accent/50"
59
+ }`}
60
+ >
61
+ <span className="text-foreground text-sm font-medium">{CURRENCY_NAMES[currency]}</span>
62
+ <span className="text-muted-foreground text-xs font-medium">{CURRENCY_SYMBOLS[currency]}</span>
63
+ </DropdownMenuItem>
64
+ {currency === "SOL" && <DropdownMenuSeparator key="separator" className="bg-border my-1" />}
65
+ </div>
66
+ ))}
67
+ </DropdownMenuContent>
68
+ </DropdownMenu>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import { B3_COIN_IMAGE_URL } from "@b3dotfun/sdk/shared/constants/currency";
4
+ import { cn } from "@b3dotfun/sdk/shared/utils";
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../../global-account/react/components/ui/tooltip";
6
+ import { useCurrencyConversion } from "../hooks/useCurrencyConversion";
7
+ import { useCurrencyModalStore } from "../stores/currencyModalStore";
8
+
9
+ interface FormattedCurrencyProps {
10
+ amount: number;
11
+ showChange?: boolean;
12
+ showColor?: boolean;
13
+ className?: string;
14
+ subB3Icon?: boolean;
15
+ clickable?: boolean;
16
+ decimals?: number;
17
+ currency?: string; // Override currency (e.g., "ETH", "USDC", "B3")
18
+ }
19
+
20
+ export function FormattedCurrency({
21
+ amount,
22
+ showChange = false,
23
+ showColor = false,
24
+ className,
25
+ subB3Icon = true,
26
+ clickable = true,
27
+ decimals,
28
+ currency,
29
+ }: FormattedCurrencyProps) {
30
+ const { formatCurrencyValue, formatTooltipValue, selectedCurrency, baseCurrency } = useCurrencyConversion();
31
+ const { openModal } = useCurrencyModalStore();
32
+
33
+ // Use passed currency or fall back to selected currency
34
+ const activeCurrency = currency || selectedCurrency;
35
+
36
+ const isPositive = amount >= 0;
37
+
38
+ // Get the formatted value (using absolute value for negative numbers when showing change)
39
+ const baseAmount = showChange ? Math.abs(amount) : amount;
40
+
41
+ // Use centralized formatting from hook with optional overrides
42
+ const formattedValue = formatCurrencyValue(baseAmount, {
43
+ decimals,
44
+ currency,
45
+ });
46
+
47
+ // Generate tooltip using the centralized hook function
48
+ const baseTooltipValue = formatTooltipValue(amount, currency);
49
+
50
+ // Add change indicator if needed
51
+ const tooltipValue = showChange ? `${isPositive ? "+" : "-"}${baseTooltipValue}` : baseTooltipValue;
52
+
53
+ // Determine color class
54
+ let colorClass = "";
55
+ if (showColor) {
56
+ if (isPositive) {
57
+ colorClass = "text-green-400";
58
+ } else {
59
+ colorClass = "text-red-400";
60
+ }
61
+ }
62
+
63
+ // Add change indicator
64
+ let displayValue = formattedValue;
65
+ if (showChange) {
66
+ if (isPositive) {
67
+ displayValue = `+${formattedValue}`;
68
+ } else {
69
+ displayValue = `-${formattedValue}`;
70
+ }
71
+ }
72
+
73
+ const handleClick = () => {
74
+ if (clickable) {
75
+ openModal();
76
+ }
77
+ };
78
+
79
+ return (
80
+ <Tooltip>
81
+ <TooltipTrigger asChild>
82
+ <span
83
+ onClick={handleClick}
84
+ className={cn(
85
+ "inline-flex items-center gap-1 whitespace-nowrap",
86
+ colorClass,
87
+ className,
88
+ clickable && "cursor-pointer transition-opacity hover:opacity-80",
89
+ )}
90
+ >
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
+ )}
101
+ </span>
102
+ </TooltipTrigger>
103
+ <TooltipContent>{tooltipValue}</TooltipContent>
104
+ </Tooltip>
105
+ );
106
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./CurrencySelector";
2
+ export * from "./FormattedCurrency";
@@ -0,0 +1,308 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { renderHook } from "@testing-library/react";
3
+ import { useCurrencyConversion } from "../useCurrencyConversion";
4
+
5
+ // Mock the external dependencies
6
+ // Store mock rates for different quote currencies
7
+ const mockRates: Record<string, number | undefined> = {};
8
+
9
+ // Mock store state
10
+ const mockStoreState: any = {
11
+ selectedCurrency: "B3",
12
+ baseCurrency: "B3",
13
+ setSelectedCurrency: vi.fn(),
14
+ setBaseCurrency: vi.fn(),
15
+ };
16
+
17
+ vi.mock("@b3dotfun/sdk/global-account/react", () => ({
18
+ useExchangeRate: vi.fn((params: any) => {
19
+ const rate = mockRates[params?.quoteCurrency];
20
+ return { rate };
21
+ }),
22
+ }));
23
+
24
+ vi.mock("@b3dotfun/sdk/shared/utils/number", () => ({
25
+ formatDisplayNumber: vi.fn((value: any) => {
26
+ const num = Number(value);
27
+ if (isNaN(num)) return "0";
28
+ return num.toLocaleString("en-US", { maximumFractionDigits: 6 });
29
+ }),
30
+ }));
31
+
32
+ vi.mock("../../stores/currencyStore", () => ({
33
+ useCurrencyStore: vi.fn((selector: any) => {
34
+ if (selector) {
35
+ return selector(mockStoreState);
36
+ }
37
+ return mockStoreState;
38
+ }),
39
+ CURRENCY_SYMBOLS: {
40
+ B3: "B3",
41
+ USD: "$",
42
+ EUR: "€",
43
+ GBP: "£",
44
+ JPY: "¥",
45
+ CAD: "C$",
46
+ AUD: "A$",
47
+ ETH: "ETH",
48
+ SOL: "SOL",
49
+ KRW: "₩",
50
+ },
51
+ }));
52
+
53
+ describe("useCurrencyConversion", () => {
54
+ beforeEach(() => {
55
+ vi.clearAllMocks();
56
+ // Reset mock rates to default
57
+ Object.keys(mockRates).forEach(key => delete mockRates[key]);
58
+ mockRates.USD = 1.0;
59
+ // Reset store state
60
+ mockStoreState.selectedCurrency = "B3";
61
+ mockStoreState.baseCurrency = "B3";
62
+ });
63
+
64
+ describe("formatCurrencyValue", () => {
65
+ it("should format base currency (B3) without conversion", () => {
66
+ mockStoreState.selectedCurrency = "B3";
67
+ mockStoreState.baseCurrency = "B3";
68
+
69
+ const { result } = renderHook(() => useCurrencyConversion());
70
+ const formatted = result.current.formatCurrencyValue(100);
71
+
72
+ expect(formatted).toContain("B3");
73
+ expect(formatted).toContain("100");
74
+ });
75
+
76
+ it("should show base currency when exchange rate is unavailable", () => {
77
+ mockRates.USD = undefined;
78
+ mockStoreState.selectedCurrency = "USD";
79
+ mockStoreState.baseCurrency = "B3";
80
+
81
+ const { result } = renderHook(() => useCurrencyConversion());
82
+ const formatted = result.current.formatCurrencyValue(100);
83
+
84
+ expect(formatted).toContain("B3");
85
+ expect(formatted).not.toContain("$");
86
+ });
87
+
88
+ it("should format USD with prefix symbol", () => {
89
+ mockRates.USD = 2.0;
90
+ mockStoreState.selectedCurrency = "USD";
91
+ mockStoreState.baseCurrency = "B3";
92
+
93
+ const { result } = renderHook(() => useCurrencyConversion());
94
+ const formatted = result.current.formatCurrencyValue(100);
95
+
96
+ expect(formatted).toMatch(/^\$/);
97
+ expect(formatted).toContain("200");
98
+ });
99
+
100
+ it("should format EUR with prefix symbol", () => {
101
+ mockRates.EUR = 1.8;
102
+ mockRates.USD = 2.0;
103
+ mockStoreState.selectedCurrency = "EUR";
104
+ mockStoreState.baseCurrency = "B3";
105
+
106
+ const { result } = renderHook(() => useCurrencyConversion());
107
+ const formatted = result.current.formatCurrencyValue(100);
108
+
109
+ expect(formatted).toMatch(/^€/);
110
+ });
111
+
112
+ it("should format JPY without decimals", () => {
113
+ mockRates.JPY = 150;
114
+ mockRates.USD = 2.0;
115
+ mockStoreState.selectedCurrency = "JPY";
116
+ mockStoreState.baseCurrency = "B3";
117
+
118
+ const { result } = renderHook(() => useCurrencyConversion());
119
+ const formatted = result.current.formatCurrencyValue(100);
120
+
121
+ expect(formatted).toContain("¥");
122
+ expect(formatted).not.toContain(".");
123
+ });
124
+
125
+ it("should format KRW without decimals", () => {
126
+ mockRates.KRW = 1300;
127
+ mockRates.USD = 2.0;
128
+ mockStoreState.selectedCurrency = "KRW";
129
+ mockStoreState.baseCurrency = "B3";
130
+
131
+ const { result } = renderHook(() => useCurrencyConversion());
132
+ const formatted = result.current.formatCurrencyValue(100);
133
+
134
+ expect(formatted).toContain("₩");
135
+ expect(formatted).not.toContain(".");
136
+ });
137
+
138
+ it("should format ETH with suffix symbol", () => {
139
+ mockRates.ETH = 0.0005;
140
+ mockRates.USD = 2.0;
141
+ mockStoreState.selectedCurrency = "ETH";
142
+ mockStoreState.baseCurrency = "B3";
143
+
144
+ const { result } = renderHook(() => useCurrencyConversion());
145
+ const formatted = result.current.formatCurrencyValue(100);
146
+
147
+ expect(formatted).toContain("ETH");
148
+ expect(formatted).not.toMatch(/^ETH/);
149
+ });
150
+
151
+ it("should format SOL with suffix symbol", () => {
152
+ mockRates.SOL = 0.05;
153
+ mockRates.USD = 2.0;
154
+ mockStoreState.selectedCurrency = "SOL";
155
+ mockStoreState.baseCurrency = "B3";
156
+
157
+ const { result } = renderHook(() => useCurrencyConversion());
158
+ const formatted = result.current.formatCurrencyValue(100);
159
+
160
+ expect(formatted).toContain("SOL");
161
+ expect(formatted).not.toMatch(/^SOL/);
162
+ });
163
+
164
+ it("should handle small USD amounts with proper conversion", () => {
165
+ mockRates.USD = 1.5;
166
+ mockStoreState.selectedCurrency = "USD";
167
+ mockStoreState.baseCurrency = "B3";
168
+
169
+ const { result } = renderHook(() => useCurrencyConversion());
170
+ const formatted = result.current.formatCurrencyValue(10);
171
+
172
+ // 10 * 1.5 = 15
173
+ expect(formatted).toMatch(/^\$/);
174
+ expect(formatted).toContain("15");
175
+ });
176
+
177
+ it("should apply correct exchange rate conversion", () => {
178
+ const testRate = 3.5;
179
+ mockRates.USD = testRate;
180
+ mockStoreState.selectedCurrency = "USD";
181
+ mockStoreState.baseCurrency = "B3";
182
+
183
+ const { result } = renderHook(() => useCurrencyConversion());
184
+ const inputValue = 100;
185
+ const formatted = result.current.formatCurrencyValue(inputValue);
186
+
187
+ expect(formatted).toContain("350");
188
+ });
189
+ });
190
+
191
+ describe("return values", () => {
192
+ it("should return selected currency", () => {
193
+ mockStoreState.selectedCurrency = "USD";
194
+ mockStoreState.baseCurrency = "B3";
195
+
196
+ const { result } = renderHook(() => useCurrencyConversion());
197
+
198
+ expect(result.current.selectedCurrency).toBe("USD");
199
+ });
200
+
201
+ it("should return base currency", () => {
202
+ mockStoreState.selectedCurrency = "USD";
203
+ mockStoreState.baseCurrency = "B3";
204
+
205
+ const { result } = renderHook(() => useCurrencyConversion());
206
+
207
+ expect(result.current.baseCurrency).toBe("B3");
208
+ });
209
+
210
+ it("should return exchange rate", () => {
211
+ const testRate = 2.5;
212
+ mockRates.USD = testRate;
213
+ mockStoreState.selectedCurrency = "USD";
214
+ mockStoreState.baseCurrency = "B3";
215
+
216
+ const { result } = renderHook(() => useCurrencyConversion());
217
+
218
+ expect(result.current.exchangeRate).toBe(testRate);
219
+ });
220
+
221
+ it("should return correct currency symbols", () => {
222
+ mockStoreState.selectedCurrency = "EUR";
223
+ mockStoreState.baseCurrency = "B3";
224
+
225
+ const { result } = renderHook(() => useCurrencyConversion());
226
+
227
+ expect(result.current.selectedCurrencySymbol).toBe("€");
228
+ expect(result.current.baseCurrencySymbol).toBe("B3");
229
+ });
230
+ });
231
+
232
+ describe("formatTooltipValue", () => {
233
+ it("should show USD equivalent when displaying base currency", () => {
234
+ mockRates.USD = 1.5;
235
+ mockStoreState.selectedCurrency = "B3";
236
+ mockStoreState.baseCurrency = "B3";
237
+
238
+ const { result } = renderHook(() => useCurrencyConversion());
239
+ const tooltip = result.current.formatTooltipValue(100);
240
+
241
+ expect(tooltip).toContain("USD");
242
+ expect(tooltip).toContain("150");
243
+ });
244
+
245
+ it("should show base currency when displaying other currency", () => {
246
+ mockRates.EUR = 0.9;
247
+ mockRates.USD = 1.2;
248
+ mockStoreState.selectedCurrency = "EUR";
249
+ mockStoreState.baseCurrency = "B3";
250
+
251
+ const { result } = renderHook(() => useCurrencyConversion());
252
+ const tooltip = result.current.formatTooltipValue(100);
253
+
254
+ expect(tooltip).toContain("B3");
255
+ expect(tooltip).toContain("100");
256
+ });
257
+
258
+ it("should handle custom currency for base currency", () => {
259
+ mockRates.USD = 2.0;
260
+ mockRates.EUR = 1.8;
261
+ mockStoreState.selectedCurrency = "EUR";
262
+ mockStoreState.baseCurrency = "B3";
263
+
264
+ const { result } = renderHook(() => useCurrencyConversion());
265
+ const tooltip = result.current.formatTooltipValue(100, "B3");
266
+
267
+ expect(tooltip).toContain("USD");
268
+ expect(tooltip).toContain("200");
269
+ });
270
+
271
+ it("should handle custom currency for non-base currency", () => {
272
+ mockRates.USD = 2.0;
273
+ mockStoreState.selectedCurrency = "USD";
274
+ mockStoreState.baseCurrency = "B3";
275
+
276
+ const { result } = renderHook(() => useCurrencyConversion());
277
+ const tooltip = result.current.formatTooltipValue(50, "ETH");
278
+
279
+ expect(tooltip).toContain("ETH");
280
+ expect(tooltip).toContain("50");
281
+ });
282
+
283
+ it("should handle absolute values for negative amounts", () => {
284
+ mockRates.USD = 1.5;
285
+ mockStoreState.selectedCurrency = "B3";
286
+ mockStoreState.baseCurrency = "B3";
287
+
288
+ const { result } = renderHook(() => useCurrencyConversion());
289
+ const tooltip = result.current.formatTooltipValue(-100);
290
+
291
+ expect(tooltip).toContain("USD");
292
+ expect(tooltip).toContain("150");
293
+ expect(tooltip).not.toContain("-");
294
+ });
295
+
296
+ it("should handle exchange rate unavailable", () => {
297
+ mockRates.USD = undefined;
298
+ mockStoreState.selectedCurrency = "B3";
299
+ mockStoreState.baseCurrency = "B3";
300
+
301
+ const { result } = renderHook(() => useCurrencyConversion());
302
+ const tooltip = result.current.formatTooltipValue(100);
303
+
304
+ expect(tooltip).toContain("USD");
305
+ expect(tooltip).toContain("100");
306
+ });
307
+ });
308
+ });
@@ -1 +1,2 @@
1
+ export * from "./useCurrencyConversion";
1
2
  export * from "./useNavigation";