@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.
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +4 -0
- package/dist/cjs/anyspend/utils/accountStore.d.ts +7 -0
- package/dist/cjs/anyspend/utils/accountStore.js +8 -0
- package/dist/cjs/anyspend/utils/index.d.ts +1 -0
- package/dist/cjs/anyspend/utils/index.js +1 -0
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +17 -0
- package/dist/cjs/shared/react/components/CurrencySelector.js +8 -3
- package/dist/cjs/shared/react/components/FormattedCurrency.d.ts +3 -3
- package/dist/cjs/shared/react/components/FormattedCurrency.js +31 -26
- package/dist/cjs/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
- package/dist/cjs/shared/react/hooks/useCurrencyConversion.js +153 -94
- package/dist/cjs/shared/react/stores/currencyStore.d.ts +83 -8
- package/dist/cjs/shared/react/stores/currencyStore.js +147 -5
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +5 -1
- package/dist/esm/anyspend/utils/accountStore.d.ts +7 -0
- package/dist/esm/anyspend/utils/accountStore.js +5 -0
- package/dist/esm/anyspend/utils/index.d.ts +1 -0
- package/dist/esm/anyspend/utils/index.js +1 -0
- package/dist/esm/global-account/react/components/B3DynamicModal.js +17 -0
- package/dist/esm/shared/react/components/CurrencySelector.js +10 -5
- package/dist/esm/shared/react/components/FormattedCurrency.d.ts +3 -3
- package/dist/esm/shared/react/components/FormattedCurrency.js +31 -26
- package/dist/esm/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
- package/dist/esm/shared/react/hooks/useCurrencyConversion.js +154 -95
- package/dist/esm/shared/react/stores/currencyStore.d.ts +83 -8
- package/dist/esm/shared/react/stores/currencyStore.js +143 -5
- package/dist/types/anyspend/utils/accountStore.d.ts +7 -0
- package/dist/types/anyspend/utils/index.d.ts +1 -0
- package/dist/types/shared/react/components/FormattedCurrency.d.ts +3 -3
- package/dist/types/shared/react/hooks/useCurrencyConversion.d.ts +8 -5
- package/dist/types/shared/react/stores/currencyStore.d.ts +83 -8
- package/package.json +1 -1
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +6 -2
- package/src/anyspend/utils/accountStore.ts +12 -0
- package/src/anyspend/utils/index.ts +1 -0
- package/src/global-account/react/components/B3DynamicModal.tsx +20 -0
- package/src/shared/react/components/CurrencySelector.tsx +36 -5
- package/src/shared/react/components/FormattedCurrency.tsx +36 -30
- package/src/shared/react/hooks/__tests__/useCurrencyConversion.test.ts +14 -14
- package/src/shared/react/hooks/useCurrencyConversion.ts +163 -96
- package/src/shared/react/stores/currencyStore.ts +216 -10
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.CryptoPaymentMethodType = void 0;
|
|
5
5
|
exports.CryptoPaymentMethod = CryptoPaymentMethod;
|
|
6
6
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const utils_1 = require("../../../../anyspend/utils");
|
|
7
8
|
const react_1 = require("../../../../global-account/react");
|
|
8
9
|
const cn_1 = require("../../../../shared/utils/cn");
|
|
9
10
|
const formatAddress_1 = require("../../../../shared/utils/formatAddress");
|
|
@@ -32,6 +33,8 @@ function CryptoPaymentMethod({ selectedPaymentMethod, setSelectedPaymentMethod,
|
|
|
32
33
|
const [showWalletModal, setShowWalletModal] = (0, react_3.useState)(false);
|
|
33
34
|
const setActiveWallet = (0, react_4.useSetActiveWallet)();
|
|
34
35
|
const { data: eoaWalletInfo } = (0, react_4.useWalletInfo)(connectedEOAWallet?.id);
|
|
36
|
+
const activeWallet = (0, react_4.useActiveWallet)();
|
|
37
|
+
const setGlobalAccountWallet = (0, utils_1.useGlobalWalletState)(state => state.setGlobalAccountWallet);
|
|
35
38
|
const isConnected = !!connectedEOAWallet;
|
|
36
39
|
const globalAddress = connectedSmartWallet?.getAccount()?.address;
|
|
37
40
|
// Helper function to check if two addresses are the same
|
|
@@ -173,6 +176,7 @@ function CryptoPaymentMethod({ selectedPaymentMethod, setSelectedPaymentMethod,
|
|
|
173
176
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "crypto-payment-method mx-auto h-fit w-[460px] max-w-full", children: [(0, jsx_runtime_1.jsxs)("div", { className: (0, cn_1.cn)("relative flex flex-col gap-10"), children: [(0, jsx_runtime_1.jsx)("button", { onClick: onBack, className: "text-as-quaternary hover:text-as-primary absolute flex h-8 w-8 items-center justify-center rounded-lg transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { className: "h-6 w-6" }) }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-around gap-4", children: (0, jsx_runtime_1.jsx)("div", { className: "flex-1 text-center", children: (0, jsx_runtime_1.jsx)("h2", { className: "text-as-primary text-lg font-semibold", children: "Select a payment method" }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "crypto-payment-methods flex flex-col gap-4", children: [(shouldShowConnectedEOA || shouldShowWagmiWallet || globalAddress) && ((0, jsx_runtime_1.jsxs)("div", { className: "installed-wallets", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-as-primary/80 mb-3 text-sm font-medium", children: "Connected wallets" }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [shouldShowConnectedEOA && ((0, jsx_runtime_1.jsx)("button", { onClick: () => {
|
|
174
177
|
setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
175
178
|
onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
179
|
+
setGlobalAccountWallet(activeWallet);
|
|
176
180
|
setActiveWallet(connectedEOAWallet);
|
|
177
181
|
sonner_1.toast.success(`Selected ${eoaWalletInfo?.name || connector?.name || "wallet"}`);
|
|
178
182
|
}, className: (0, cn_1.cn)("crypto-payment-method-connect-wallet w-full rounded-xl border p-4 text-left transition-all hover:shadow-md", selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Wallet } from "thirdweb/wallets";
|
|
2
|
+
interface GlobalWalletState {
|
|
3
|
+
globalAccountWallet?: Wallet;
|
|
4
|
+
setGlobalAccountWallet: (account?: Wallet) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare const useGlobalWalletState: import("zustand").UseBoundStore<import("zustand").StoreApi<GlobalWalletState>>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useGlobalWalletState = void 0;
|
|
4
|
+
const zustand_1 = require("zustand");
|
|
5
|
+
exports.useGlobalWalletState = (0, zustand_1.create)(set => ({
|
|
6
|
+
globalAccountWallet: undefined,
|
|
7
|
+
setGlobalAccountWallet: account => set({ globalAccountWallet: account }),
|
|
8
|
+
}));
|
|
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./accountStore"), exports);
|
|
17
18
|
__exportStar(require("./address"), exports);
|
|
18
19
|
__exportStar(require("./chain"), exports);
|
|
19
20
|
__exportStar(require("./format"), exports);
|
|
@@ -5,9 +5,11 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("../../../anyspend/react");
|
|
6
6
|
const AnyspendDepositHype_1 = require("../../../anyspend/react/components/AnyspendDepositHype");
|
|
7
7
|
const AnySpendStakeUpside_1 = require("../../../anyspend/react/components/AnySpendStakeUpside");
|
|
8
|
+
const utils_1 = require("../../../anyspend/utils");
|
|
8
9
|
const react_2 = require("../../../global-account/react");
|
|
9
10
|
const cn_1 = require("../../../shared/utils/cn");
|
|
10
11
|
const debug_1 = require("../../../shared/utils/debug");
|
|
12
|
+
const react_3 = require("react");
|
|
11
13
|
const AvatarEditor_1 = require("./AvatarEditor/AvatarEditor");
|
|
12
14
|
const useB3_1 = require("./B3Provider/useB3");
|
|
13
15
|
const LinkAccount_1 = require("./LinkAccount/LinkAccount");
|
|
@@ -16,11 +18,26 @@ const RequestPermissions_1 = require("./RequestPermissions/RequestPermissions");
|
|
|
16
18
|
const SignInWithB3Flow_1 = require("./SignInWithB3/SignInWithB3Flow");
|
|
17
19
|
const dialog_1 = require("./ui/dialog");
|
|
18
20
|
const drawer_1 = require("./ui/drawer");
|
|
21
|
+
const react_4 = require("thirdweb/react");
|
|
19
22
|
const debug = (0, debug_1.debugB3React)("B3DynamicModal");
|
|
20
23
|
function B3DynamicModal() {
|
|
21
24
|
const { isOpen, setB3ModalOpen, contentType, history, navigateBack } = (0, react_2.useModalStore)();
|
|
22
25
|
const { theme } = (0, useB3_1.useB3)();
|
|
23
26
|
const isMobile = (0, react_2.useIsMobile)();
|
|
27
|
+
const prevIsOpenRef = (0, react_3.useRef)(isOpen);
|
|
28
|
+
const globalAccountWallet = (0, utils_1.useGlobalWalletState)(state => state.globalAccountWallet);
|
|
29
|
+
const setGlobalAccountWallet = (0, utils_1.useGlobalWalletState)(state => state.setGlobalAccountWallet);
|
|
30
|
+
const setActiveWallet = (0, react_4.useSetActiveWallet)();
|
|
31
|
+
// anyspend cleanup global account chnages by setting account back
|
|
32
|
+
(0, react_3.useEffect)(() => {
|
|
33
|
+
if (prevIsOpenRef.current && !isOpen) {
|
|
34
|
+
if (globalAccountWallet) {
|
|
35
|
+
setActiveWallet(globalAccountWallet);
|
|
36
|
+
setGlobalAccountWallet(undefined);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
prevIsOpenRef.current = isOpen;
|
|
40
|
+
}, [isOpen, globalAccountWallet, setActiveWallet, setGlobalAccountWallet]);
|
|
24
41
|
// Define arrays for different modal type groups
|
|
25
42
|
const fullWidthTypes = [
|
|
26
43
|
"anySpend",
|
|
@@ -7,8 +7,13 @@ const utils_1 = require("../../../shared/utils");
|
|
|
7
7
|
const button_1 = require("../../../global-account/react/components/ui/button");
|
|
8
8
|
const dropdown_menu_1 = require("../../../global-account/react/components/ui/dropdown-menu");
|
|
9
9
|
const currencyStore_1 = require("../stores/currencyStore");
|
|
10
|
-
const
|
|
10
|
+
const builtInCurrencies = ["B3", "ETH", "SOL", "USD", "EUR", "GBP", "KRW", "JPY", "CAD", "AUD"];
|
|
11
11
|
function CurrencySelector({ labelClassName, buttonVariant = "dark", label }) {
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const selectedCurrency = (0, currencyStore_1.useCurrencyStore)(state => state.selectedCurrency);
|
|
13
|
+
const setSelectedCurrency = (0, currencyStore_1.useCurrencyStore)(state => state.setSelectedCurrency);
|
|
14
|
+
const customCurrencies = (0, currencyStore_1.useCurrencyStore)(state => state.customCurrencies);
|
|
15
|
+
// Separate built-in and custom for better organization
|
|
16
|
+
const customCurrencyCodes = Object.keys(customCurrencies);
|
|
17
|
+
const hasCustomCurrencies = customCurrencyCodes.length > 0;
|
|
18
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-2", children: (0, jsx_runtime_1.jsxs)(dropdown_menu_1.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(dropdown_menu_1.DropdownMenuTrigger, { asChild: true, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [label && ((0, jsx_runtime_1.jsx)("span", { className: (0, utils_1.cn)("text-foreground text-sm font-medium leading-none tracking-tight sm:text-base", labelClassName), children: label })), (0, jsx_runtime_1.jsxs)(button_1.Button, { variant: buttonVariant, className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium", children: (0, currencyStore_1.getCurrencyName)(selectedCurrency) }), (0, jsx_runtime_1.jsx)("svg", { className: "h-4 w-4", fill: "currentColor", viewBox: "0 0 20 20", children: (0, jsx_runtime_1.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" }) })] })] }) }), (0, jsx_runtime_1.jsxs)(dropdown_menu_1.DropdownMenuContent, { align: "end", className: "z-[100] min-w-[200px]", children: [builtInCurrencies.map(currency => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)(dropdown_menu_1.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: [(0, jsx_runtime_1.jsx)("span", { className: "text-foreground text-sm font-medium", children: currencyStore_1.CURRENCY_NAMES[currency] }), (0, jsx_runtime_1.jsx)("span", { className: "text-muted-foreground text-xs font-medium", children: currencyStore_1.CURRENCY_SYMBOLS[currency] })] }), currency === "SOL" && (0, jsx_runtime_1.jsx)(dropdown_menu_1.DropdownMenuSeparator, { className: "bg-border my-1" }, "separator")] }, currency))), hasCustomCurrencies && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dropdown_menu_1.DropdownMenuSeparator, { className: "bg-border my-1" }), customCurrencyCodes.map(currency => ((0, jsx_runtime_1.jsxs)(dropdown_menu_1.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: [(0, jsx_runtime_1.jsx)("span", { className: "text-foreground text-sm font-medium", children: (0, currencyStore_1.getCurrencyName)(currency) }), (0, jsx_runtime_1.jsx)("span", { className: "text-muted-foreground text-xs font-medium", children: (0, currencyStore_1.getCurrencySymbol)(currency) })] }, currency)))] }))] })] }) }));
|
|
14
19
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
interface FormattedCurrencyProps {
|
|
2
|
-
amount:
|
|
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,
|
|
11
|
+
export declare function FormattedCurrency({ amount, sourceCurrency, showChange, showColor, className, subB3Icon, clickable, decimals, }: FormattedCurrencyProps): import("react/jsx-runtime").JSX.Element;
|
|
12
12
|
export {};
|
|
@@ -8,21 +8,30 @@ const utils_1 = require("../../../shared/utils");
|
|
|
8
8
|
const tooltip_1 = require("../../../global-account/react/components/ui/tooltip");
|
|
9
9
|
const useCurrencyConversion_1 = require("../hooks/useCurrencyConversion");
|
|
10
10
|
const currencyModalStore_1 = require("../stores/currencyModalStore");
|
|
11
|
-
|
|
11
|
+
const currencyStore_1 = require("../stores/currencyStore");
|
|
12
|
+
function FormattedCurrency({ amount, sourceCurrency, showChange = false, showColor = false, className, subB3Icon = true, clickable = true, decimals, }) {
|
|
12
13
|
const { formatCurrencyValue, formatTooltipValue, selectedCurrency, baseCurrency } = (0, useCurrencyConversion_1.useCurrencyConversion)();
|
|
13
14
|
const { openModal } = (0, currencyModalStore_1.useCurrencyModalStore)();
|
|
14
|
-
//
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
15
|
+
// Convert from smallest unit to human-readable using currency's decimal places
|
|
16
|
+
const decimalPlaces = (0, currencyStore_1.getCurrencyDecimalPlaces)(sourceCurrency);
|
|
17
|
+
const divisor = Math.pow(10, decimalPlaces);
|
|
18
|
+
// Parse amount - handle both string and numeric inputs, including negatives
|
|
19
|
+
let parsedAmount;
|
|
20
|
+
if (typeof amount === "string") {
|
|
21
|
+
// Handle BigInt strings and negative values
|
|
22
|
+
const numericAmount = amount.startsWith("-") ? -Math.abs(parseFloat(amount.replace("-", ""))) : parseFloat(amount);
|
|
23
|
+
parsedAmount = numericAmount / divisor;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
parsedAmount = amount / divisor;
|
|
27
|
+
}
|
|
28
|
+
const isPositive = parsedAmount >= 0;
|
|
29
|
+
// Always format with absolute value, we'll add the sign separately
|
|
30
|
+
const absoluteAmount = Math.abs(parsedAmount);
|
|
31
|
+
// Format value with automatic conversion from source to display currency
|
|
32
|
+
const formattedValue = formatCurrencyValue(absoluteAmount, sourceCurrency, { decimals });
|
|
24
33
|
// Generate tooltip using the centralized hook function
|
|
25
|
-
const baseTooltipValue = formatTooltipValue(
|
|
34
|
+
const baseTooltipValue = formatTooltipValue(parsedAmount, sourceCurrency);
|
|
26
35
|
// Add change indicator if needed
|
|
27
36
|
const tooltipValue = showChange ? `${isPositive ? "+" : "-"}${baseTooltipValue}` : baseTooltipValue;
|
|
28
37
|
// Determine color class
|
|
@@ -35,26 +44,22 @@ function FormattedCurrency({ amount, showChange = false, showColor = false, clas
|
|
|
35
44
|
colorClass = "text-red-400";
|
|
36
45
|
}
|
|
37
46
|
}
|
|
38
|
-
//
|
|
47
|
+
// Build display value with appropriate sign
|
|
39
48
|
let displayValue = formattedValue;
|
|
40
49
|
if (showChange) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
50
|
+
// Add +/- prefix for change indicators
|
|
51
|
+
displayValue = `${isPositive ? "+" : "-"}${formattedValue}`;
|
|
52
|
+
}
|
|
53
|
+
else if (!isPositive) {
|
|
54
|
+
// Add minus sign for negative values
|
|
55
|
+
displayValue = `-${formattedValue}`;
|
|
47
56
|
}
|
|
48
57
|
const handleClick = () => {
|
|
49
58
|
if (clickable) {
|
|
50
59
|
openModal();
|
|
51
60
|
}
|
|
52
61
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
? displayValue.split(" ")[0]
|
|
57
|
-
: displayValue, subB3Icon &&
|
|
58
|
-
(currency === baseCurrency || (!currency && activeCurrency === baseCurrency)) &&
|
|
59
|
-
baseCurrency === "B3" && ((0, jsx_runtime_1.jsx)("img", { src: currency_1.B3_COIN_IMAGE_URL, className: "inline-block h-4 w-4 align-middle", alt: "B3 coin" }))] }) }), (0, jsx_runtime_1.jsx)(tooltip_1.TooltipContent, { children: tooltipValue })] }));
|
|
62
|
+
// Check if we should show B3 icon (when displaying in B3 and baseCurrency is B3)
|
|
63
|
+
const shouldShowB3Icon = subB3Icon && selectedCurrency === "B3" && baseCurrency === "B3";
|
|
64
|
+
return ((0, jsx_runtime_1.jsxs)(tooltip_1.Tooltip, { children: [(0, jsx_runtime_1.jsx)(tooltip_1.TooltipTrigger, { asChild: true, children: (0, jsx_runtime_1.jsxs)("span", { onClick: handleClick, className: (0, utils_1.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 && ((0, jsx_runtime_1.jsx)("img", { src: currency_1.B3_COIN_IMAGE_URL, className: "inline-block h-4 w-4 align-middle", alt: "B3 coin" }))] }) }), (0, jsx_runtime_1.jsx)(tooltip_1.TooltipContent, { children: tooltipValue })] }));
|
|
60
65
|
}
|
|
@@ -16,20 +16,23 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export declare function useCurrencyConversion(): {
|
|
18
18
|
/** Currently selected display currency */
|
|
19
|
-
selectedCurrency:
|
|
19
|
+
selectedCurrency: string;
|
|
20
20
|
/** Base currency used for conversion (typically B3) */
|
|
21
|
-
baseCurrency:
|
|
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,
|
|
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
|
};
|
|
@@ -40,8 +40,10 @@ async function fetchAllExchangeRates(baseCurrency) {
|
|
|
40
40
|
function useCurrencyConversion() {
|
|
41
41
|
const selectedCurrency = (0, currencyStore_1.useCurrencyStore)(state => state.selectedCurrency);
|
|
42
42
|
const baseCurrency = (0, currencyStore_1.useCurrencyStore)(state => state.baseCurrency);
|
|
43
|
-
|
|
44
|
-
const
|
|
43
|
+
const getCustomExchangeRate = (0, currencyStore_1.useCurrencyStore)(state => state.getExchangeRate);
|
|
44
|
+
const customCurrencies = (0, currencyStore_1.useCurrencyStore)(state => state.customCurrencies);
|
|
45
|
+
// Fetch all exchange rates for the base currency from Coinbase API
|
|
46
|
+
const { data: apiExchangeRates } = (0, react_query_1.useQuery)({
|
|
45
47
|
queryKey: ["exchangeRates", baseCurrency],
|
|
46
48
|
queryFn: () => fetchAllExchangeRates(baseCurrency),
|
|
47
49
|
refetchInterval: REFETCH_INTERVAL_MS,
|
|
@@ -49,155 +51,208 @@ function useCurrencyConversion() {
|
|
|
49
51
|
retry: 3,
|
|
50
52
|
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, REFETCH_INTERVAL_MS),
|
|
51
53
|
});
|
|
52
|
-
// Extract specific rates from the full rates object
|
|
53
|
-
const exchangeRate = exchangeRates?.[selectedCurrency];
|
|
54
|
-
const usdRate = exchangeRates?.["USD"];
|
|
55
54
|
/**
|
|
56
|
-
*
|
|
55
|
+
* Get exchange rate between two currencies, checking custom rates first, then API rates.
|
|
56
|
+
* Supports chaining through base currency for custom currencies.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
* -
|
|
60
|
-
* -
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
* Examples:
|
|
59
|
+
* - WIN → USD: Checks WIN→USD custom rate, then chains WIN→B3→USD
|
|
60
|
+
* - BTC → EUR: Checks BTC→EUR custom rate, then chains BTC→B3→EUR
|
|
61
|
+
*/
|
|
62
|
+
const getExchangeRate = (from, to) => {
|
|
63
|
+
// If same currency, rate is 1
|
|
64
|
+
if (from === to)
|
|
65
|
+
return 1;
|
|
66
|
+
// 1. Check direct custom exchange rate first
|
|
67
|
+
const directCustomRate = getCustomExchangeRate(from, to);
|
|
68
|
+
if (directCustomRate !== undefined) {
|
|
69
|
+
return directCustomRate;
|
|
70
|
+
}
|
|
71
|
+
// 2. Check direct API rate (from base currency)
|
|
72
|
+
if (from === baseCurrency && apiExchangeRates) {
|
|
73
|
+
return apiExchangeRates[to];
|
|
74
|
+
}
|
|
75
|
+
// 3. Try to chain through base currency using custom rates
|
|
76
|
+
// e.g., WIN → B3 → USD (where WIN→B3 is custom, B3→USD is API)
|
|
77
|
+
const customFromToBase = getCustomExchangeRate(from, baseCurrency);
|
|
78
|
+
if (customFromToBase !== undefined) {
|
|
79
|
+
// We have a custom rate from 'from' to base
|
|
80
|
+
// Now get rate from base to 'to'
|
|
81
|
+
const baseToTo = apiExchangeRates?.[to] ?? getCustomExchangeRate(baseCurrency, to);
|
|
82
|
+
if (baseToTo !== undefined) {
|
|
83
|
+
return customFromToBase * baseToTo;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// 4. Try reverse: chain from base currency through custom rate
|
|
87
|
+
// e.g., USD → B3 → WIN (where B3→WIN is custom)
|
|
88
|
+
const customBaseToTo = getCustomExchangeRate(baseCurrency, to);
|
|
89
|
+
if (customBaseToTo !== undefined && apiExchangeRates) {
|
|
90
|
+
// We have a custom rate from base to 'to'
|
|
91
|
+
// Now get rate from 'from' to base
|
|
92
|
+
const fromToBase = apiExchangeRates[from];
|
|
93
|
+
if (fromToBase !== undefined && fromToBase !== 0) {
|
|
94
|
+
return fromToBase * customBaseToTo;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 5. Fall back to pure API conversion through base
|
|
98
|
+
// e.g., EUR to GBP = (EUR to B3) * (B3 to GBP)
|
|
99
|
+
if (apiExchangeRates) {
|
|
100
|
+
const fromToBase = apiExchangeRates[from];
|
|
101
|
+
const baseToTo = apiExchangeRates[to];
|
|
102
|
+
if (fromToBase && baseToTo && fromToBase !== 0) {
|
|
103
|
+
return baseToTo / fromToBase;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
};
|
|
108
|
+
// Extract specific rates
|
|
109
|
+
const exchangeRate = getExchangeRate(baseCurrency, selectedCurrency);
|
|
110
|
+
/**
|
|
111
|
+
* Formats a numeric value as a currency string with automatic conversion.
|
|
112
|
+
*
|
|
113
|
+
* New behavior:
|
|
114
|
+
* - Takes the SOURCE currency (what the value is in) as a required parameter
|
|
115
|
+
* - Automatically converts from source → display currency (selected in picker)
|
|
116
|
+
* - Supports multi-hop conversions (e.g., WIN → B3 → USD)
|
|
117
|
+
* - Applies currency-specific formatting rules to the TARGET currency
|
|
65
118
|
*
|
|
66
|
-
* @param value - The numeric value to format
|
|
119
|
+
* @param value - The numeric value to format
|
|
120
|
+
* @param sourceCurrency - The currency the value is currently in (e.g., "WIN", "B3", "USD")
|
|
67
121
|
* @param options - Optional formatting overrides
|
|
68
|
-
* @param options.decimals - Override number of decimal places
|
|
69
|
-
* @param options.currency - Override currency (bypasses conversion)
|
|
122
|
+
* @param options.decimals - Override number of decimal places for display
|
|
70
123
|
* @returns Formatted currency string with appropriate symbol and decimal places
|
|
71
124
|
*
|
|
72
125
|
* @example
|
|
73
126
|
* ```tsx
|
|
74
|
-
*
|
|
75
|
-
* formatCurrencyValue(
|
|
76
|
-
*
|
|
77
|
-
*
|
|
127
|
+
* // Value is 3031 WIN, user has USD selected
|
|
128
|
+
* formatCurrencyValue(3031, "WIN") // Returns "$30.31" (converts WIN→B3→USD)
|
|
129
|
+
*
|
|
130
|
+
* // Value is 100 B3, user has B3 selected (no conversion)
|
|
131
|
+
* formatCurrencyValue(100, "B3") // Returns "100 B3"
|
|
132
|
+
*
|
|
133
|
+
* // Value is 50 USD, user has EUR selected
|
|
134
|
+
* formatCurrencyValue(50, "USD") // Returns "€45.50" (converts USD→EUR)
|
|
78
135
|
* ```
|
|
79
136
|
*/
|
|
80
|
-
const formatCurrencyValue = (value, options) => {
|
|
81
|
-
const overrideCurrency = options?.currency;
|
|
137
|
+
const formatCurrencyValue = (value, sourceCurrency, options) => {
|
|
82
138
|
const overrideDecimals = options?.decimals;
|
|
83
|
-
//
|
|
84
|
-
if (
|
|
85
|
-
const
|
|
139
|
+
// If source and display currency are the same, no conversion needed
|
|
140
|
+
if (sourceCurrency === selectedCurrency) {
|
|
141
|
+
const customMetadata = (0, currencyStore_1.getCurrencyMetadata)(sourceCurrency);
|
|
142
|
+
const decimalsToUse = overrideDecimals !== undefined ? overrideDecimals : customMetadata?.decimals;
|
|
86
143
|
const formatted = (0, number_1.formatDisplayNumber)(value, {
|
|
87
144
|
fractionDigits: decimalsToUse,
|
|
88
|
-
|
|
145
|
+
significantDigits: decimalsToUse === undefined ? 6 : undefined,
|
|
146
|
+
showSubscripts: customMetadata?.showSubscripts ?? false,
|
|
89
147
|
});
|
|
90
|
-
|
|
148
|
+
const symbol = (0, currencyStore_1.getCurrencySymbol)(sourceCurrency);
|
|
149
|
+
const usePrefix = customMetadata?.prefixSymbol ?? ["USD", "EUR", "GBP", "CAD", "AUD"].includes(sourceCurrency);
|
|
150
|
+
return usePrefix ? `${symbol}${formatted}` : `${formatted} ${symbol}`;
|
|
91
151
|
}
|
|
92
|
-
//
|
|
93
|
-
|
|
152
|
+
// Get exchange rate from source to display currency
|
|
153
|
+
const conversionRate = getExchangeRate(sourceCurrency, selectedCurrency);
|
|
154
|
+
// If no conversion rate available, display in source currency
|
|
155
|
+
if (conversionRate === undefined) {
|
|
156
|
+
const customMetadata = (0, currencyStore_1.getCurrencyMetadata)(sourceCurrency);
|
|
94
157
|
const formatted = (0, number_1.formatDisplayNumber)(value, {
|
|
158
|
+
significantDigits: 6,
|
|
159
|
+
showSubscripts: customMetadata?.showSubscripts ?? false,
|
|
160
|
+
});
|
|
161
|
+
const symbol = (0, currencyStore_1.getCurrencySymbol)(sourceCurrency);
|
|
162
|
+
return `${formatted} ${symbol}`;
|
|
163
|
+
}
|
|
164
|
+
// Convert value
|
|
165
|
+
const convertedValue = value * conversionRate;
|
|
166
|
+
// Get symbol and metadata for display currency
|
|
167
|
+
const symbol = (0, currencyStore_1.getCurrencySymbol)(selectedCurrency);
|
|
168
|
+
const customMetadata = (0, currencyStore_1.getCurrencyMetadata)(selectedCurrency);
|
|
169
|
+
const usePrefix = customMetadata?.prefixSymbol ?? ["USD", "EUR", "GBP", "CAD", "AUD"].includes(selectedCurrency);
|
|
170
|
+
let formatted;
|
|
171
|
+
// Apply formatting based on display currency
|
|
172
|
+
if (overrideDecimals !== undefined) {
|
|
173
|
+
formatted = (0, number_1.formatDisplayNumber)(convertedValue, {
|
|
95
174
|
fractionDigits: overrideDecimals,
|
|
96
175
|
showSubscripts: false,
|
|
97
176
|
});
|
|
98
|
-
return `${formatted} ${baseCurrency}`;
|
|
99
177
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
significantDigits:
|
|
104
|
-
showSubscripts:
|
|
178
|
+
else if (customMetadata) {
|
|
179
|
+
formatted = (0, number_1.formatDisplayNumber)(convertedValue, {
|
|
180
|
+
fractionDigits: customMetadata.decimals,
|
|
181
|
+
significantDigits: customMetadata.decimals === undefined ? 6 : undefined,
|
|
182
|
+
showSubscripts: customMetadata.showSubscripts ?? false,
|
|
105
183
|
});
|
|
106
|
-
return `${formatted} ${baseCurrency}`;
|
|
107
184
|
}
|
|
108
|
-
|
|
109
|
-
const convertedValue = value * exchangeRate;
|
|
110
|
-
const symbol = currencyStore_1.CURRENCY_SYMBOLS[selectedCurrency];
|
|
111
|
-
// Currencies that display symbol before the number (e.g., $100.00)
|
|
112
|
-
const prefixCurrencies = ["USD", "EUR", "GBP", "CAD", "AUD"];
|
|
113
|
-
let formatted;
|
|
114
|
-
if (selectedCurrency === "JPY" || selectedCurrency === "KRW") {
|
|
115
|
-
// Japanese Yen and Korean Won don't use decimal places
|
|
185
|
+
else if (selectedCurrency === "JPY" || selectedCurrency === "KRW") {
|
|
116
186
|
formatted = (0, number_1.formatDisplayNumber)(convertedValue, {
|
|
117
187
|
fractionDigits: 0,
|
|
118
188
|
showSubscripts: false,
|
|
119
189
|
});
|
|
120
190
|
}
|
|
121
191
|
else if (selectedCurrency === "ETH" || selectedCurrency === "SOL") {
|
|
122
|
-
// Crypto currencies use more precision and subscript notation
|
|
123
|
-
// for very small amounts (e.g., 0.0₃45 ETH)
|
|
124
192
|
formatted = (0, number_1.formatDisplayNumber)(convertedValue, {
|
|
125
193
|
significantDigits: 6,
|
|
126
194
|
showSubscripts: true,
|
|
127
195
|
});
|
|
128
196
|
}
|
|
129
197
|
else {
|
|
130
|
-
// Standard fiat currencies (USD, EUR, GBP, CAD, AUD)
|
|
131
|
-
// Use 2 decimal places minimum for amounts under 1000
|
|
132
198
|
formatted = (0, number_1.formatDisplayNumber)(convertedValue, {
|
|
133
199
|
significantDigits: 6,
|
|
134
200
|
fractionDigits: convertedValue < 1000 ? 2 : undefined,
|
|
135
201
|
showSubscripts: true,
|
|
136
202
|
});
|
|
137
203
|
}
|
|
138
|
-
|
|
139
|
-
if (prefixCurrencies.includes(selectedCurrency)) {
|
|
140
|
-
return `${symbol}${formatted}`;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
// Suffix currencies: JPY, KRW, ETH, SOL, B3
|
|
144
|
-
return `${formatted} ${symbol}`;
|
|
145
|
-
}
|
|
204
|
+
return usePrefix ? `${symbol}${formatted}` : `${formatted} ${symbol}`;
|
|
146
205
|
};
|
|
147
206
|
/**
|
|
148
207
|
* Formats a tooltip value showing the alternate currency representation.
|
|
149
208
|
*
|
|
150
|
-
*
|
|
151
|
-
* -
|
|
152
|
-
* - When displaying
|
|
153
|
-
* -
|
|
209
|
+
* New behavior:
|
|
210
|
+
* - Takes the SOURCE currency (what the value is in)
|
|
211
|
+
* - When displaying in non-USD: Shows USD equivalent in tooltip
|
|
212
|
+
* - When displaying in USD: Shows source currency in tooltip
|
|
154
213
|
*
|
|
155
214
|
* @param value - The numeric value to format
|
|
156
|
-
* @param
|
|
215
|
+
* @param sourceCurrency - The currency the value is currently in
|
|
157
216
|
* @returns Formatted tooltip string
|
|
158
217
|
*
|
|
159
218
|
* @example
|
|
160
219
|
* ```tsx
|
|
161
|
-
*
|
|
162
|
-
* formatTooltipValue(
|
|
220
|
+
* // Value is 3031 WIN, displaying as EUR
|
|
221
|
+
* formatTooltipValue(3031, "WIN") // Returns "$30.31 USD"
|
|
222
|
+
*
|
|
223
|
+
* // Value is 100 B3, displaying as USD
|
|
224
|
+
* formatTooltipValue(100, "B3") // Returns "100 B3"
|
|
163
225
|
* ```
|
|
164
226
|
*/
|
|
165
|
-
const formatTooltipValue = (value,
|
|
166
|
-
const displayCurrency = customCurrency || selectedCurrency;
|
|
227
|
+
const formatTooltipValue = (value, sourceCurrency) => {
|
|
167
228
|
const absoluteValue = Math.abs(value);
|
|
168
|
-
//
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
showSubscripts: true,
|
|
177
|
-
});
|
|
178
|
-
return `$${formatted} USD`;
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
// Show as-is for other custom currencies
|
|
182
|
-
return `${(0, number_1.formatDisplayNumber)(absoluteValue, { significantDigits: 6 })} ${customCurrency}`;
|
|
183
|
-
}
|
|
229
|
+
// If displaying in USD, show source currency in tooltip
|
|
230
|
+
if (selectedCurrency === "USD") {
|
|
231
|
+
const formatted = (0, number_1.formatDisplayNumber)(absoluteValue, {
|
|
232
|
+
significantDigits: 6,
|
|
233
|
+
showSubscripts: (0, currencyStore_1.getCurrencyMetadata)(sourceCurrency)?.showSubscripts ?? false,
|
|
234
|
+
});
|
|
235
|
+
const symbol = (0, currencyStore_1.getCurrencySymbol)(sourceCurrency);
|
|
236
|
+
return `${formatted} ${symbol}`;
|
|
184
237
|
}
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
238
|
+
// Otherwise, show USD equivalent in tooltip
|
|
239
|
+
const usdRate = getExchangeRate(sourceCurrency, "USD");
|
|
240
|
+
if (usdRate === undefined) {
|
|
241
|
+
// Fallback to source currency if no USD rate
|
|
242
|
+
const formatted = (0, number_1.formatDisplayNumber)(absoluteValue, {
|
|
189
243
|
significantDigits: 6,
|
|
190
|
-
|
|
191
|
-
showSubscripts: true,
|
|
244
|
+
showSubscripts: (0, currencyStore_1.getCurrencyMetadata)(sourceCurrency)?.showSubscripts ?? false,
|
|
192
245
|
});
|
|
193
|
-
|
|
246
|
+
const symbol = (0, currencyStore_1.getCurrencySymbol)(sourceCurrency);
|
|
247
|
+
return `${formatted} ${symbol}`;
|
|
194
248
|
}
|
|
195
|
-
|
|
196
|
-
const formatted = (0, number_1.formatDisplayNumber)(
|
|
197
|
-
significantDigits:
|
|
249
|
+
const usdValue = absoluteValue * usdRate;
|
|
250
|
+
const formatted = (0, number_1.formatDisplayNumber)(usdValue, {
|
|
251
|
+
significantDigits: 6,
|
|
252
|
+
fractionDigits: usdValue < 1000 ? 2 : undefined,
|
|
198
253
|
showSubscripts: true,
|
|
199
254
|
});
|
|
200
|
-
return
|
|
255
|
+
return `$${formatted} USD`;
|
|
201
256
|
};
|
|
202
257
|
return {
|
|
203
258
|
/** Currently selected display currency */
|
|
@@ -211,8 +266,12 @@ function useCurrencyConversion() {
|
|
|
211
266
|
/** Format a tooltip value showing alternate currency representation */
|
|
212
267
|
formatTooltipValue,
|
|
213
268
|
/** Symbol for the currently selected currency (e.g., "$", "€", "ETH") */
|
|
214
|
-
selectedCurrencySymbol: currencyStore_1.
|
|
269
|
+
selectedCurrencySymbol: (0, currencyStore_1.getCurrencySymbol)(selectedCurrency),
|
|
215
270
|
/** Symbol for the base currency */
|
|
216
|
-
baseCurrencySymbol: currencyStore_1.
|
|
271
|
+
baseCurrencySymbol: (0, currencyStore_1.getCurrencySymbol)(baseCurrency),
|
|
272
|
+
/** Get exchange rate between any two currencies */
|
|
273
|
+
getExchangeRate,
|
|
274
|
+
/** All registered custom currencies */
|
|
275
|
+
customCurrencies,
|
|
217
276
|
};
|
|
218
277
|
}
|