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

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 (23) hide show
  1. package/dist/cjs/shared/react/components/CurrencySelector.js +8 -3
  2. package/dist/cjs/shared/react/components/FormattedCurrency.d.ts +3 -3
  3. package/dist/cjs/shared/react/components/FormattedCurrency.js +31 -26
  4. package/dist/cjs/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
  5. package/dist/cjs/shared/react/hooks/useCurrencyConversion.js +153 -94
  6. package/dist/cjs/shared/react/stores/currencyStore.d.ts +83 -8
  7. package/dist/cjs/shared/react/stores/currencyStore.js +147 -5
  8. package/dist/esm/shared/react/components/CurrencySelector.js +10 -5
  9. package/dist/esm/shared/react/components/FormattedCurrency.d.ts +3 -3
  10. package/dist/esm/shared/react/components/FormattedCurrency.js +31 -26
  11. package/dist/esm/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
  12. package/dist/esm/shared/react/hooks/useCurrencyConversion.js +154 -95
  13. package/dist/esm/shared/react/stores/currencyStore.d.ts +83 -8
  14. package/dist/esm/shared/react/stores/currencyStore.js +143 -5
  15. package/dist/types/shared/react/components/FormattedCurrency.d.ts +3 -3
  16. package/dist/types/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
  17. package/dist/types/shared/react/stores/currencyStore.d.ts +83 -8
  18. package/package.json +1 -1
  19. package/src/shared/react/components/CurrencySelector.tsx +36 -5
  20. package/src/shared/react/components/FormattedCurrency.tsx +36 -30
  21. package/src/shared/react/hooks/__tests__/useCurrencyConversion.test.ts +14 -14
  22. package/src/shared/react/hooks/useCurrencyConversion.ts +163 -96
  23. package/src/shared/react/stores/currencyStore.ts +216 -10
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useCurrencyStore = exports.CURRENCY_NAMES = exports.CURRENCY_SYMBOLS = void 0;
4
+ exports.getCurrencySymbol = getCurrencySymbol;
5
+ exports.getCurrencyName = getCurrencyName;
6
+ exports.getCurrencyMetadata = getCurrencyMetadata;
7
+ exports.getCurrencyDecimalPlaces = getCurrencyDecimalPlaces;
4
8
  const zustand_1 = require("zustand");
5
9
  const middleware_1 = require("zustand/middleware");
6
10
  /**
@@ -38,20 +42,158 @@ exports.CURRENCY_NAMES = {
38
42
  /**
39
43
  * Zustand store for managing currency selection and conversion.
40
44
  * Persists user's selected currency preference in localStorage.
45
+ * Supports dynamic currency registration and custom exchange rates.
41
46
  *
42
47
  * @example
43
48
  * ```tsx
44
- * const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
45
- * // Change display currency to USD
46
- * setSelectedCurrency('USD');
49
+ * const { selectedCurrency, setSelectedCurrency, addCurrency, setExchangeRate } = useCurrencyStore();
50
+ *
51
+ * // Add a new currency
52
+ * addCurrency({
53
+ * code: "BTC",
54
+ * symbol: "₿",
55
+ * name: "Bitcoin",
56
+ * showSubscripts: true,
57
+ * });
58
+ *
59
+ * // Set exchange rate: 1 BTC = 50000 USD
60
+ * setExchangeRate("BTC", "USD", 50000);
61
+ *
62
+ * // Change display currency
63
+ * setSelectedCurrency('BTC');
47
64
  * ```
48
65
  */
49
- exports.useCurrencyStore = (0, zustand_1.create)()((0, middleware_1.persist)(set => ({
66
+ exports.useCurrencyStore = (0, zustand_1.create)()((0, middleware_1.persist)((set, get) => ({
50
67
  selectedCurrency: "B3",
51
68
  baseCurrency: "B3",
69
+ customCurrencies: {},
70
+ customExchangeRates: {},
52
71
  setSelectedCurrency: currency => set({ selectedCurrency: currency }),
53
72
  setBaseCurrency: currency => set({ baseCurrency: currency }),
73
+ addCurrency: metadata => {
74
+ set(state => ({
75
+ customCurrencies: {
76
+ ...state.customCurrencies,
77
+ [metadata.code]: metadata,
78
+ },
79
+ }));
80
+ },
81
+ removeCurrency: code => {
82
+ set(state => {
83
+ // Remove the currency
84
+ const { [code]: _removed, ...remaining } = state.customCurrencies;
85
+ // Remove all exchange rates involving this currency
86
+ const filteredRates = {};
87
+ for (const [key, rate] of Object.entries(state.customExchangeRates)) {
88
+ // Key format is "FROM-TO", skip if either matches the removed code
89
+ const [from, to] = key.split("-");
90
+ if (from !== code && to !== code) {
91
+ filteredRates[key] = rate;
92
+ }
93
+ }
94
+ return {
95
+ customCurrencies: remaining,
96
+ customExchangeRates: filteredRates,
97
+ };
98
+ });
99
+ },
100
+ setExchangeRate: (from, to, rate) => {
101
+ set(state => {
102
+ const key = `${from}-${to}`;
103
+ const inverseKey = `${to}-${from}`;
104
+ // Only set inverse rate if rate is not 0 (to avoid Infinity)
105
+ const newRates = {
106
+ ...state.customExchangeRates,
107
+ [key]: rate,
108
+ };
109
+ if (rate !== 0) {
110
+ newRates[inverseKey] = 1 / rate;
111
+ }
112
+ return {
113
+ customExchangeRates: newRates,
114
+ };
115
+ });
116
+ },
117
+ getExchangeRate: (from, to) => {
118
+ const key = `${from}-${to}`;
119
+ return get().customExchangeRates[key];
120
+ },
121
+ getAllCurrencies: () => {
122
+ const builtIn = Object.keys(exports.CURRENCY_SYMBOLS);
123
+ const custom = Object.keys(get().customCurrencies);
124
+ return [...builtIn, ...custom];
125
+ },
54
126
  }), {
55
127
  name: "currency-storage",
56
- version: 2,
128
+ version: 3,
57
129
  }));
130
+ /**
131
+ * Get the symbol for any currency (built-in or custom).
132
+ */
133
+ function getCurrencySymbol(currency) {
134
+ // Check built-in currencies first
135
+ if (currency in exports.CURRENCY_SYMBOLS) {
136
+ return exports.CURRENCY_SYMBOLS[currency];
137
+ }
138
+ // Check custom currencies
139
+ const customCurrencies = exports.useCurrencyStore.getState().customCurrencies;
140
+ const customCurrency = customCurrencies[currency];
141
+ if (customCurrency) {
142
+ return customCurrency.symbol;
143
+ }
144
+ // Fallback to currency code
145
+ return currency;
146
+ }
147
+ /**
148
+ * Get the name for any currency (built-in or custom).
149
+ */
150
+ function getCurrencyName(currency) {
151
+ // Check built-in currencies first
152
+ if (currency in exports.CURRENCY_NAMES) {
153
+ return exports.CURRENCY_NAMES[currency];
154
+ }
155
+ // Check custom currencies
156
+ const customCurrencies = exports.useCurrencyStore.getState().customCurrencies;
157
+ const customCurrency = customCurrencies[currency];
158
+ if (customCurrency) {
159
+ return customCurrency.name;
160
+ }
161
+ // Fallback to currency code
162
+ return currency;
163
+ }
164
+ /**
165
+ * Get metadata for a custom currency.
166
+ */
167
+ function getCurrencyMetadata(currency) {
168
+ const customCurrencies = exports.useCurrencyStore.getState().customCurrencies;
169
+ return customCurrencies[currency];
170
+ }
171
+ /**
172
+ * Get the number of decimal places for a currency (for converting from smallest unit).
173
+ * Used when parsing amounts from wei/smallest unit format.
174
+ *
175
+ * @param currency - Currency code
176
+ * @returns Number of decimal places (e.g., 18 for ETH/wei, 2 for USD cents, 0 for JPY)
177
+ */
178
+ function getCurrencyDecimalPlaces(currency) {
179
+ // Check custom currencies first
180
+ const customCurrencies = exports.useCurrencyStore.getState().customCurrencies;
181
+ const customMetadata = customCurrencies[currency];
182
+ if (customMetadata?.decimals !== undefined) {
183
+ return customMetadata.decimals;
184
+ }
185
+ // Built-in currencies with 18 decimals (wei-like)
186
+ if (currency === "WIN" || currency === "ETH" || currency === "SOL" || currency === "B3") {
187
+ return 18;
188
+ }
189
+ // Fiat currencies with cent-like decimals
190
+ if (currency === "USD" || currency === "EUR" || currency === "GBP" || currency === "CAD" || currency === "AUD") {
191
+ return 2;
192
+ }
193
+ // Currencies without fractional units
194
+ if (currency === "JPY" || currency === "KRW") {
195
+ return 0;
196
+ }
197
+ // Default to 18 decimals (wei-like)
198
+ return 18;
199
+ }
@@ -1,11 +1,16 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { cn } from "../../../shared/utils/index.js";
4
4
  import { Button } from "../../../global-account/react/components/ui/button.js";
5
5
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "../../../global-account/react/components/ui/dropdown-menu.js";
6
- import { CURRENCY_NAMES, CURRENCY_SYMBOLS, useCurrencyStore } from "../stores/currencyStore.js";
7
- const currencies = ["B3", "ETH", "SOL", "USD", "EUR", "GBP", "KRW", "JPY", "CAD", "AUD"];
6
+ import { CURRENCY_NAMES, CURRENCY_SYMBOLS, useCurrencyStore, getCurrencyName, getCurrencySymbol, } from "../stores/currencyStore.js";
7
+ const builtInCurrencies = ["B3", "ETH", "SOL", "USD", "EUR", "GBP", "KRW", "JPY", "CAD", "AUD"];
8
8
  export function CurrencySelector({ labelClassName, buttonVariant = "dark", label }) {
9
- const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
10
- return (_jsx("div", { className: "flex items-center gap-2", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("div", { className: "flex items-center gap-3", children: [label && (_jsx("span", { className: cn("text-foreground text-sm font-medium leading-none tracking-tight sm:text-base", labelClassName), children: label })), _jsxs(Button, { variant: buttonVariant, className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: CURRENCY_NAMES[selectedCurrency] }), _jsx("svg", { className: "h-4 w-4", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", 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", clipRule: "evenodd" }) })] })] }) }), _jsx(DropdownMenuContent, { align: "end", className: "z-[100] min-w-[200px]", children: currencies.map(currency => (_jsxs("div", { children: [_jsxs(DropdownMenuItem, { onClick: () => setSelectedCurrency(currency), className: `flex cursor-pointer items-center justify-between gap-3 px-3 py-2.5 transition-colors ${selectedCurrency === currency ? "bg-accent" : "hover:bg-accent/50"}`, children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: CURRENCY_NAMES[currency] }), _jsx("span", { className: "text-muted-foreground text-xs font-medium", children: CURRENCY_SYMBOLS[currency] })] }), currency === "SOL" && _jsx(DropdownMenuSeparator, { className: "bg-border my-1" }, "separator")] }, currency))) })] }) }));
9
+ const selectedCurrency = useCurrencyStore(state => state.selectedCurrency);
10
+ const setSelectedCurrency = useCurrencyStore(state => state.setSelectedCurrency);
11
+ const customCurrencies = useCurrencyStore(state => state.customCurrencies);
12
+ // Separate built-in and custom for better organization
13
+ const customCurrencyCodes = Object.keys(customCurrencies);
14
+ const hasCustomCurrencies = customCurrencyCodes.length > 0;
15
+ return (_jsx("div", { className: "flex items-center gap-2", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("div", { className: "flex items-center gap-3", children: [label && (_jsx("span", { className: cn("text-foreground text-sm font-medium leading-none tracking-tight sm:text-base", labelClassName), children: label })), _jsxs(Button, { variant: buttonVariant, className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: getCurrencyName(selectedCurrency) }), _jsx("svg", { className: "h-4 w-4", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { fillRule: "evenodd", 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", clipRule: "evenodd" }) })] })] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "z-[100] min-w-[200px]", children: [builtInCurrencies.map(currency => (_jsxs("div", { children: [_jsxs(DropdownMenuItem, { onClick: () => setSelectedCurrency(currency), className: `flex cursor-pointer items-center justify-between gap-3 px-3 py-2.5 transition-colors ${selectedCurrency === currency ? "bg-accent" : "hover:bg-accent/50"}`, children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: CURRENCY_NAMES[currency] }), _jsx("span", { className: "text-muted-foreground text-xs font-medium", children: CURRENCY_SYMBOLS[currency] })] }), currency === "SOL" && _jsx(DropdownMenuSeparator, { className: "bg-border my-1" }, "separator")] }, currency))), hasCustomCurrencies && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, { className: "bg-border my-1" }), customCurrencyCodes.map(currency => (_jsxs(DropdownMenuItem, { onClick: () => setSelectedCurrency(currency), className: `flex cursor-pointer items-center justify-between gap-3 px-3 py-2.5 transition-colors ${selectedCurrency === currency ? "bg-accent" : "hover:bg-accent/50"}`, children: [_jsx("span", { className: "text-foreground text-sm font-medium", children: getCurrencyName(currency) }), _jsx("span", { className: "text-muted-foreground text-xs font-medium", children: getCurrencySymbol(currency) })] }, currency)))] }))] })] }) }));
11
16
  }
@@ -1,12 +1,12 @@
1
1
  interface FormattedCurrencyProps {
2
- amount: number;
2
+ amount: string;
3
+ sourceCurrency: string;
3
4
  showChange?: boolean;
4
5
  showColor?: boolean;
5
6
  className?: string;
6
7
  subB3Icon?: boolean;
7
8
  clickable?: boolean;
8
9
  decimals?: number;
9
- currency?: string;
10
10
  }
11
- export declare function FormattedCurrency({ amount, showChange, showColor, className, subB3Icon, clickable, decimals, currency, }: FormattedCurrencyProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function FormattedCurrency({ amount, sourceCurrency, showChange, showColor, className, subB3Icon, clickable, decimals, }: FormattedCurrencyProps): import("react/jsx-runtime").JSX.Element;
12
12
  export {};
@@ -5,21 +5,30 @@ import { cn } from "../../../shared/utils/index.js";
5
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../../global-account/react/components/ui/tooltip.js";
6
6
  import { useCurrencyConversion } from "../hooks/useCurrencyConversion.js";
7
7
  import { useCurrencyModalStore } from "../stores/currencyModalStore.js";
8
- export function FormattedCurrency({ amount, showChange = false, showColor = false, className, subB3Icon = true, clickable = true, decimals, currency, }) {
8
+ import { getCurrencyDecimalPlaces } from "../stores/currencyStore.js";
9
+ export function FormattedCurrency({ amount, sourceCurrency, showChange = false, showColor = false, className, subB3Icon = true, clickable = true, decimals, }) {
9
10
  const { formatCurrencyValue, formatTooltipValue, selectedCurrency, baseCurrency } = useCurrencyConversion();
10
11
  const { openModal } = useCurrencyModalStore();
11
- // Use passed currency or fall back to selected currency
12
- const activeCurrency = currency || selectedCurrency;
13
- const isPositive = amount >= 0;
14
- // Get the formatted value (using absolute value for negative numbers when showing change)
15
- const baseAmount = showChange ? Math.abs(amount) : amount;
16
- // Use centralized formatting from hook with optional overrides
17
- const formattedValue = formatCurrencyValue(baseAmount, {
18
- decimals,
19
- currency,
20
- });
12
+ // Convert from smallest unit to human-readable using currency's decimal places
13
+ const decimalPlaces = getCurrencyDecimalPlaces(sourceCurrency);
14
+ const divisor = Math.pow(10, decimalPlaces);
15
+ // Parse amount - handle both string and numeric inputs, including negatives
16
+ let parsedAmount;
17
+ if (typeof amount === "string") {
18
+ // Handle BigInt strings and negative values
19
+ const numericAmount = amount.startsWith("-") ? -Math.abs(parseFloat(amount.replace("-", ""))) : parseFloat(amount);
20
+ parsedAmount = numericAmount / divisor;
21
+ }
22
+ else {
23
+ parsedAmount = amount / divisor;
24
+ }
25
+ const isPositive = parsedAmount >= 0;
26
+ // Always format with absolute value, we'll add the sign separately
27
+ const absoluteAmount = Math.abs(parsedAmount);
28
+ // Format value with automatic conversion from source to display currency
29
+ const formattedValue = formatCurrencyValue(absoluteAmount, sourceCurrency, { decimals });
21
30
  // Generate tooltip using the centralized hook function
22
- const baseTooltipValue = formatTooltipValue(amount, currency);
31
+ const baseTooltipValue = formatTooltipValue(parsedAmount, sourceCurrency);
23
32
  // Add change indicator if needed
24
33
  const tooltipValue = showChange ? `${isPositive ? "+" : "-"}${baseTooltipValue}` : baseTooltipValue;
25
34
  // Determine color class
@@ -32,26 +41,22 @@ export function FormattedCurrency({ amount, showChange = false, showColor = fals
32
41
  colorClass = "text-red-400";
33
42
  }
34
43
  }
35
- // Add change indicator
44
+ // Build display value with appropriate sign
36
45
  let displayValue = formattedValue;
37
46
  if (showChange) {
38
- if (isPositive) {
39
- displayValue = `+${formattedValue}`;
40
- }
41
- else {
42
- displayValue = `-${formattedValue}`;
43
- }
47
+ // Add +/- prefix for change indicators
48
+ displayValue = `${isPositive ? "+" : "-"}${formattedValue}`;
49
+ }
50
+ else if (!isPositive) {
51
+ // Add minus sign for negative values
52
+ displayValue = `-${formattedValue}`;
44
53
  }
45
54
  const handleClick = () => {
46
55
  if (clickable) {
47
56
  openModal();
48
57
  }
49
58
  };
50
- return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("span", { onClick: handleClick, className: cn("inline-flex items-center gap-1 whitespace-nowrap", colorClass, className, clickable && "cursor-pointer transition-opacity hover:opacity-80"), children: [subB3Icon &&
51
- (currency === baseCurrency || (!currency && activeCurrency === baseCurrency)) &&
52
- baseCurrency === "B3"
53
- ? displayValue.split(" ")[0]
54
- : displayValue, subB3Icon &&
55
- (currency === baseCurrency || (!currency && activeCurrency === baseCurrency)) &&
56
- baseCurrency === "B3" && (_jsx("img", { src: B3_COIN_IMAGE_URL, className: "inline-block h-4 w-4 align-middle", alt: "B3 coin" }))] }) }), _jsx(TooltipContent, { children: tooltipValue })] }));
59
+ // Check if we should show B3 icon (when displaying in B3 and baseCurrency is B3)
60
+ const shouldShowB3Icon = subB3Icon && selectedCurrency === "B3" && baseCurrency === "B3";
61
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("span", { onClick: handleClick, className: cn("inline-flex items-center gap-1 whitespace-nowrap", colorClass, className, clickable && "cursor-pointer transition-opacity hover:opacity-80"), children: [shouldShowB3Icon ? displayValue.split(" ")[0] : displayValue, shouldShowB3Icon && (_jsx("img", { src: B3_COIN_IMAGE_URL, className: "inline-block h-4 w-4 align-middle", alt: "B3 coin" }))] }) }), _jsx(TooltipContent, { children: tooltipValue })] }));
57
62
  }
@@ -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
  };