@b3dotfun/sdk 0.0.58-alpha.3 → 0.0.58-test.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.
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +0 -4
- package/dist/cjs/anyspend/utils/index.d.ts +0 -1
- package/dist/cjs/anyspend/utils/index.js +0 -1
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +0 -17
- package/dist/cjs/global-account/react/hooks/useWagmiConfig.d.ts +1 -441
- package/dist/cjs/global-account/react/hooks/useWagmiConfig.js +0 -2
- package/dist/cjs/shared/react/components/CurrencySelector.js +3 -8
- package/dist/cjs/shared/react/components/FormattedCurrency.d.ts +3 -3
- package/dist/cjs/shared/react/components/FormattedCurrency.js +26 -31
- package/dist/cjs/shared/react/hooks/useCurrencyConversion.d.ts +5 -8
- package/dist/cjs/shared/react/hooks/useCurrencyConversion.js +94 -153
- package/dist/cjs/shared/react/stores/currencyStore.d.ts +8 -83
- package/dist/cjs/shared/react/stores/currencyStore.js +5 -147
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +1 -5
- package/dist/esm/anyspend/utils/index.d.ts +0 -1
- package/dist/esm/anyspend/utils/index.js +0 -1
- package/dist/esm/global-account/react/components/B3DynamicModal.js +0 -17
- package/dist/esm/global-account/react/hooks/useWagmiConfig.d.ts +1 -441
- package/dist/esm/global-account/react/hooks/useWagmiConfig.js +0 -2
- package/dist/esm/shared/react/components/CurrencySelector.js +5 -10
- package/dist/esm/shared/react/components/FormattedCurrency.d.ts +3 -3
- package/dist/esm/shared/react/components/FormattedCurrency.js +26 -31
- package/dist/esm/shared/react/hooks/useCurrencyConversion.d.ts +5 -8
- package/dist/esm/shared/react/hooks/useCurrencyConversion.js +95 -154
- package/dist/esm/shared/react/stores/currencyStore.d.ts +8 -83
- package/dist/esm/shared/react/stores/currencyStore.js +5 -143
- package/dist/types/anyspend/utils/index.d.ts +0 -1
- package/dist/types/global-account/react/hooks/useWagmiConfig.d.ts +1 -441
- package/dist/types/shared/react/components/FormattedCurrency.d.ts +3 -3
- package/dist/types/shared/react/hooks/useCurrencyConversion.d.ts +5 -8
- package/dist/types/shared/react/stores/currencyStore.d.ts +8 -83
- package/package.json +8 -4
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +2 -6
- package/src/anyspend/utils/index.ts +0 -1
- package/src/global-account/react/components/B3DynamicModal.tsx +0 -20
- package/src/global-account/react/hooks/useWagmiConfig.tsx +0 -2
- package/src/shared/react/components/CurrencySelector.tsx +5 -36
- package/src/shared/react/components/FormattedCurrency.tsx +30 -36
- package/src/shared/react/hooks/__tests__/useCurrencyConversion.test.ts +14 -14
- package/src/shared/react/hooks/useCurrencyConversion.ts +96 -163
- package/src/shared/react/stores/currencyStore.ts +10 -216
- package/dist/cjs/anyspend/utils/accountStore.d.ts +0 -7
- package/dist/cjs/anyspend/utils/accountStore.js +0 -8
- package/dist/esm/anyspend/utils/accountStore.d.ts +0 -7
- package/dist/esm/anyspend/utils/accountStore.js +0 -5
- package/dist/types/anyspend/utils/accountStore.d.ts +0 -7
- package/src/anyspend/utils/accountStore.ts +0 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useQuery } from "@tanstack/react-query";
|
|
2
2
|
import { formatDisplayNumber } from "../../../shared/utils/number.js";
|
|
3
|
-
import {
|
|
3
|
+
import { CURRENCY_SYMBOLS, useCurrencyStore } from "../stores/currencyStore.js";
|
|
4
4
|
const COINBASE_API_URL = "https://api.coinbase.com/v2/exchange-rates";
|
|
5
5
|
const REFETCH_INTERVAL_MS = 30000;
|
|
6
6
|
/**
|
|
@@ -37,10 +37,8 @@ async function fetchAllExchangeRates(baseCurrency) {
|
|
|
37
37
|
export function useCurrencyConversion() {
|
|
38
38
|
const selectedCurrency = useCurrencyStore(state => state.selectedCurrency);
|
|
39
39
|
const baseCurrency = useCurrencyStore(state => state.baseCurrency);
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
// Fetch all exchange rates for the base currency from Coinbase API
|
|
43
|
-
const { data: apiExchangeRates } = useQuery({
|
|
40
|
+
// Fetch all exchange rates for the base currency
|
|
41
|
+
const { data: exchangeRates } = useQuery({
|
|
44
42
|
queryKey: ["exchangeRates", baseCurrency],
|
|
45
43
|
queryFn: () => fetchAllExchangeRates(baseCurrency),
|
|
46
44
|
refetchInterval: REFETCH_INTERVAL_MS,
|
|
@@ -48,208 +46,155 @@ export function useCurrencyConversion() {
|
|
|
48
46
|
retry: 3,
|
|
49
47
|
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, REFETCH_INTERVAL_MS),
|
|
50
48
|
});
|
|
49
|
+
// Extract specific rates from the full rates object
|
|
50
|
+
const exchangeRate = exchangeRates?.[selectedCurrency];
|
|
51
|
+
const usdRate = exchangeRates?.["USD"];
|
|
51
52
|
/**
|
|
52
|
-
*
|
|
53
|
-
* Supports chaining through base currency for custom currencies.
|
|
53
|
+
* Formats a numeric value as a currency string with proper conversion and formatting.
|
|
54
54
|
*
|
|
55
|
-
*
|
|
56
|
-
* -
|
|
57
|
-
* -
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return 1;
|
|
63
|
-
// 1. Check direct custom exchange rate first
|
|
64
|
-
const directCustomRate = getCustomExchangeRate(from, to);
|
|
65
|
-
if (directCustomRate !== undefined) {
|
|
66
|
-
return directCustomRate;
|
|
67
|
-
}
|
|
68
|
-
// 2. Check direct API rate (from base currency)
|
|
69
|
-
if (from === baseCurrency && apiExchangeRates) {
|
|
70
|
-
return apiExchangeRates[to];
|
|
71
|
-
}
|
|
72
|
-
// 3. Try to chain through base currency using custom rates
|
|
73
|
-
// e.g., WIN → B3 → USD (where WIN→B3 is custom, B3→USD is API)
|
|
74
|
-
const customFromToBase = getCustomExchangeRate(from, baseCurrency);
|
|
75
|
-
if (customFromToBase !== undefined) {
|
|
76
|
-
// We have a custom rate from 'from' to base
|
|
77
|
-
// Now get rate from base to 'to'
|
|
78
|
-
const baseToTo = apiExchangeRates?.[to] ?? getCustomExchangeRate(baseCurrency, to);
|
|
79
|
-
if (baseToTo !== undefined) {
|
|
80
|
-
return customFromToBase * baseToTo;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// 4. Try reverse: chain from base currency through custom rate
|
|
84
|
-
// e.g., USD → B3 → WIN (where B3→WIN is custom)
|
|
85
|
-
const customBaseToTo = getCustomExchangeRate(baseCurrency, to);
|
|
86
|
-
if (customBaseToTo !== undefined && apiExchangeRates) {
|
|
87
|
-
// We have a custom rate from base to 'to'
|
|
88
|
-
// Now get rate from 'from' to base
|
|
89
|
-
const fromToBase = apiExchangeRates[from];
|
|
90
|
-
if (fromToBase !== undefined && fromToBase !== 0) {
|
|
91
|
-
return fromToBase * customBaseToTo;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// 5. Fall back to pure API conversion through base
|
|
95
|
-
// e.g., EUR to GBP = (EUR to B3) * (B3 to GBP)
|
|
96
|
-
if (apiExchangeRates) {
|
|
97
|
-
const fromToBase = apiExchangeRates[from];
|
|
98
|
-
const baseToTo = apiExchangeRates[to];
|
|
99
|
-
if (fromToBase && baseToTo && fromToBase !== 0) {
|
|
100
|
-
return baseToTo / fromToBase;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return undefined;
|
|
104
|
-
};
|
|
105
|
-
// Extract specific rates
|
|
106
|
-
const exchangeRate = getExchangeRate(baseCurrency, selectedCurrency);
|
|
107
|
-
/**
|
|
108
|
-
* Formats a numeric value as a currency string with automatic conversion.
|
|
109
|
-
*
|
|
110
|
-
* New behavior:
|
|
111
|
-
* - Takes the SOURCE currency (what the value is in) as a required parameter
|
|
112
|
-
* - Automatically converts from source → display currency (selected in picker)
|
|
113
|
-
* - Supports multi-hop conversions (e.g., WIN → B3 → USD)
|
|
114
|
-
* - Applies currency-specific formatting rules to the TARGET currency
|
|
55
|
+
* Behavior:
|
|
56
|
+
* - When exchange rate is unavailable, displays value in base currency
|
|
57
|
+
* - Applies currency-specific formatting rules:
|
|
58
|
+
* - JPY/KRW: No decimal places
|
|
59
|
+
* - ETH/SOL: 6 significant digits with subscript notation for small values
|
|
60
|
+
* - Fiat (USD/EUR/GBP/CAD/AUD): 2 decimal places minimum for values < 1000
|
|
61
|
+
* - Handles symbol positioning (prefix for fiat, suffix for crypto)
|
|
115
62
|
*
|
|
116
|
-
* @param value - The numeric value to format
|
|
117
|
-
* @param sourceCurrency - The currency the value is currently in (e.g., "WIN", "B3", "USD")
|
|
63
|
+
* @param value - The numeric value to format (in base currency)
|
|
118
64
|
* @param options - Optional formatting overrides
|
|
119
|
-
* @param options.decimals - Override number of decimal places
|
|
65
|
+
* @param options.decimals - Override number of decimal places
|
|
66
|
+
* @param options.currency - Override currency (bypasses conversion)
|
|
120
67
|
* @returns Formatted currency string with appropriate symbol and decimal places
|
|
121
68
|
*
|
|
122
69
|
* @example
|
|
123
70
|
* ```tsx
|
|
124
|
-
* //
|
|
125
|
-
* formatCurrencyValue(
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* formatCurrencyValue(100, "B3") // Returns "100 B3"
|
|
129
|
-
*
|
|
130
|
-
* // Value is 50 USD, user has EUR selected
|
|
131
|
-
* formatCurrencyValue(50, "USD") // Returns "€45.50" (converts USD→EUR)
|
|
71
|
+
* formatCurrencyValue(100) // Returns "$100.00" if USD is selected
|
|
72
|
+
* formatCurrencyValue(0.0001) // Returns "0.0₄1 ETH" if ETH is selected
|
|
73
|
+
* formatCurrencyValue(1500) // Returns "¥1,500" if JPY is selected
|
|
74
|
+
* formatCurrencyValue(100, { decimals: 4, currency: "ETH" }) // Returns "100.0000 ETH"
|
|
132
75
|
* ```
|
|
133
76
|
*/
|
|
134
|
-
const formatCurrencyValue = (value,
|
|
77
|
+
const formatCurrencyValue = (value, options) => {
|
|
78
|
+
const overrideCurrency = options?.currency;
|
|
135
79
|
const overrideDecimals = options?.decimals;
|
|
136
|
-
//
|
|
137
|
-
if (
|
|
138
|
-
const
|
|
139
|
-
const decimalsToUse = overrideDecimals !== undefined ? overrideDecimals : customMetadata?.decimals;
|
|
80
|
+
// Custom currency provided - bypass conversion and use simple formatting
|
|
81
|
+
if (overrideCurrency) {
|
|
82
|
+
const decimalsToUse = overrideDecimals !== undefined ? overrideDecimals : overrideCurrency === "B3" ? 0 : 2;
|
|
140
83
|
const formatted = formatDisplayNumber(value, {
|
|
141
84
|
fractionDigits: decimalsToUse,
|
|
142
|
-
|
|
143
|
-
showSubscripts: customMetadata?.showSubscripts ?? false,
|
|
85
|
+
showSubscripts: false,
|
|
144
86
|
});
|
|
145
|
-
|
|
146
|
-
const usePrefix = customMetadata?.prefixSymbol ?? ["USD", "EUR", "GBP", "CAD", "AUD"].includes(sourceCurrency);
|
|
147
|
-
return usePrefix ? `${symbol}${formatted}` : `${formatted} ${symbol}`;
|
|
87
|
+
return `${formatted} ${overrideCurrency}`;
|
|
148
88
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
// If no conversion rate available, display in source currency
|
|
152
|
-
if (conversionRate === undefined) {
|
|
153
|
-
const customMetadata = getCurrencyMetadata(sourceCurrency);
|
|
89
|
+
// Custom decimals for base currency without conversion
|
|
90
|
+
if (overrideDecimals !== undefined && selectedCurrency === baseCurrency) {
|
|
154
91
|
const formatted = formatDisplayNumber(value, {
|
|
155
|
-
significantDigits: 6,
|
|
156
|
-
showSubscripts: customMetadata?.showSubscripts ?? false,
|
|
157
|
-
});
|
|
158
|
-
const symbol = getCurrencySymbol(sourceCurrency);
|
|
159
|
-
return `${formatted} ${symbol}`;
|
|
160
|
-
}
|
|
161
|
-
// Convert value
|
|
162
|
-
const convertedValue = value * conversionRate;
|
|
163
|
-
// Get symbol and metadata for display currency
|
|
164
|
-
const symbol = getCurrencySymbol(selectedCurrency);
|
|
165
|
-
const customMetadata = getCurrencyMetadata(selectedCurrency);
|
|
166
|
-
const usePrefix = customMetadata?.prefixSymbol ?? ["USD", "EUR", "GBP", "CAD", "AUD"].includes(selectedCurrency);
|
|
167
|
-
let formatted;
|
|
168
|
-
// Apply formatting based on display currency
|
|
169
|
-
if (overrideDecimals !== undefined) {
|
|
170
|
-
formatted = formatDisplayNumber(convertedValue, {
|
|
171
92
|
fractionDigits: overrideDecimals,
|
|
172
93
|
showSubscripts: false,
|
|
173
94
|
});
|
|
95
|
+
return `${formatted} ${baseCurrency}`;
|
|
174
96
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
significantDigits:
|
|
179
|
-
showSubscripts:
|
|
97
|
+
// If showing base currency, no conversion needed
|
|
98
|
+
if (selectedCurrency === baseCurrency || !exchangeRate) {
|
|
99
|
+
const formatted = formatDisplayNumber(value, {
|
|
100
|
+
significantDigits: baseCurrency === "B3" ? 6 : 8,
|
|
101
|
+
showSubscripts: true,
|
|
180
102
|
});
|
|
103
|
+
return `${formatted} ${baseCurrency}`;
|
|
181
104
|
}
|
|
182
|
-
|
|
105
|
+
// Convert value using current exchange rate
|
|
106
|
+
const convertedValue = value * exchangeRate;
|
|
107
|
+
const symbol = CURRENCY_SYMBOLS[selectedCurrency];
|
|
108
|
+
// Currencies that display symbol before the number (e.g., $100.00)
|
|
109
|
+
const prefixCurrencies = ["USD", "EUR", "GBP", "CAD", "AUD"];
|
|
110
|
+
let formatted;
|
|
111
|
+
if (selectedCurrency === "JPY" || selectedCurrency === "KRW") {
|
|
112
|
+
// Japanese Yen and Korean Won don't use decimal places
|
|
183
113
|
formatted = formatDisplayNumber(convertedValue, {
|
|
184
114
|
fractionDigits: 0,
|
|
185
115
|
showSubscripts: false,
|
|
186
116
|
});
|
|
187
117
|
}
|
|
188
118
|
else if (selectedCurrency === "ETH" || selectedCurrency === "SOL") {
|
|
119
|
+
// Crypto currencies use more precision and subscript notation
|
|
120
|
+
// for very small amounts (e.g., 0.0₃45 ETH)
|
|
189
121
|
formatted = formatDisplayNumber(convertedValue, {
|
|
190
122
|
significantDigits: 6,
|
|
191
123
|
showSubscripts: true,
|
|
192
124
|
});
|
|
193
125
|
}
|
|
194
126
|
else {
|
|
127
|
+
// Standard fiat currencies (USD, EUR, GBP, CAD, AUD)
|
|
128
|
+
// Use 2 decimal places minimum for amounts under 1000
|
|
195
129
|
formatted = formatDisplayNumber(convertedValue, {
|
|
196
130
|
significantDigits: 6,
|
|
197
131
|
fractionDigits: convertedValue < 1000 ? 2 : undefined,
|
|
198
132
|
showSubscripts: true,
|
|
199
133
|
});
|
|
200
134
|
}
|
|
201
|
-
|
|
135
|
+
// Apply currency symbol with correct positioning
|
|
136
|
+
if (prefixCurrencies.includes(selectedCurrency)) {
|
|
137
|
+
return `${symbol}${formatted}`;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Suffix currencies: JPY, KRW, ETH, SOL, B3
|
|
141
|
+
return `${formatted} ${symbol}`;
|
|
142
|
+
}
|
|
202
143
|
};
|
|
203
144
|
/**
|
|
204
145
|
* Formats a tooltip value showing the alternate currency representation.
|
|
205
146
|
*
|
|
206
|
-
*
|
|
207
|
-
* -
|
|
208
|
-
* - When displaying
|
|
209
|
-
* -
|
|
147
|
+
* Behavior:
|
|
148
|
+
* - When displaying base currency: Shows USD equivalent
|
|
149
|
+
* - When displaying other currency: Shows base currency equivalent
|
|
150
|
+
* - For custom currencies: Shows appropriate conversion or original value
|
|
210
151
|
*
|
|
211
152
|
* @param value - The numeric value to format
|
|
212
|
-
* @param
|
|
153
|
+
* @param customCurrency - Optional custom currency override
|
|
213
154
|
* @returns Formatted tooltip string
|
|
214
155
|
*
|
|
215
156
|
* @example
|
|
216
157
|
* ```tsx
|
|
217
|
-
* //
|
|
218
|
-
* formatTooltipValue(
|
|
219
|
-
*
|
|
220
|
-
* // Value is 100 B3, displaying as USD
|
|
221
|
-
* formatTooltipValue(100, "B3") // Returns "100 B3"
|
|
158
|
+
* formatTooltipValue(100) // Returns "$150.00 USD" if displaying B3 with rate 1.5
|
|
159
|
+
* formatTooltipValue(100, "ETH") // Returns "100.0000 ETH" if custom currency
|
|
222
160
|
* ```
|
|
223
161
|
*/
|
|
224
|
-
const formatTooltipValue = (value,
|
|
162
|
+
const formatTooltipValue = (value, customCurrency) => {
|
|
163
|
+
const displayCurrency = customCurrency || selectedCurrency;
|
|
225
164
|
const absoluteValue = Math.abs(value);
|
|
226
|
-
//
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
165
|
+
// Custom currency provided
|
|
166
|
+
if (customCurrency) {
|
|
167
|
+
if (customCurrency === baseCurrency) {
|
|
168
|
+
// Show USD equivalent for base currency using USD rate
|
|
169
|
+
const usdValue = usdRate ? absoluteValue * usdRate : absoluteValue;
|
|
170
|
+
const formatted = formatDisplayNumber(usdValue, {
|
|
171
|
+
significantDigits: 6,
|
|
172
|
+
fractionDigits: usdValue < 1000 ? 2 : undefined,
|
|
173
|
+
showSubscripts: true,
|
|
174
|
+
});
|
|
175
|
+
return `$${formatted} USD`;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Show as-is for other custom currencies
|
|
179
|
+
return `${formatDisplayNumber(absoluteValue, { significantDigits: 6 })} ${customCurrency}`;
|
|
180
|
+
}
|
|
234
181
|
}
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const formatted = formatDisplayNumber(absoluteValue, {
|
|
182
|
+
// Showing base currency - display USD equivalent
|
|
183
|
+
if (displayCurrency === baseCurrency) {
|
|
184
|
+
const usdValue = usdRate ? absoluteValue * usdRate : absoluteValue;
|
|
185
|
+
const formatted = formatDisplayNumber(usdValue, {
|
|
240
186
|
significantDigits: 6,
|
|
241
|
-
|
|
187
|
+
fractionDigits: usdValue < 1000 ? 2 : undefined,
|
|
188
|
+
showSubscripts: true,
|
|
242
189
|
});
|
|
243
|
-
|
|
244
|
-
return `${formatted} ${symbol}`;
|
|
190
|
+
return `$${formatted} USD`;
|
|
245
191
|
}
|
|
246
|
-
|
|
247
|
-
const formatted = formatDisplayNumber(
|
|
248
|
-
significantDigits: 6,
|
|
249
|
-
fractionDigits: usdValue < 1000 ? 2 : undefined,
|
|
192
|
+
// Showing other currency - display base currency equivalent
|
|
193
|
+
const formatted = formatDisplayNumber(absoluteValue, {
|
|
194
|
+
significantDigits: baseCurrency === "B3" ? 6 : 8,
|
|
250
195
|
showSubscripts: true,
|
|
251
196
|
});
|
|
252
|
-
return
|
|
197
|
+
return `${formatted} ${baseCurrency}`;
|
|
253
198
|
};
|
|
254
199
|
return {
|
|
255
200
|
/** Currently selected display currency */
|
|
@@ -263,12 +208,8 @@ export function useCurrencyConversion() {
|
|
|
263
208
|
/** Format a tooltip value showing alternate currency representation */
|
|
264
209
|
formatTooltipValue,
|
|
265
210
|
/** Symbol for the currently selected currency (e.g., "$", "€", "ETH") */
|
|
266
|
-
selectedCurrencySymbol:
|
|
211
|
+
selectedCurrencySymbol: CURRENCY_SYMBOLS[selectedCurrency],
|
|
267
212
|
/** Symbol for the base currency */
|
|
268
|
-
baseCurrencySymbol:
|
|
269
|
-
/** Get exchange rate between any two currencies */
|
|
270
|
-
getExchangeRate,
|
|
271
|
-
/** All registered custom currencies */
|
|
272
|
-
customCurrencies,
|
|
213
|
+
baseCurrencySymbol: CURRENCY_SYMBOLS[baseCurrency],
|
|
273
214
|
};
|
|
274
215
|
}
|
|
@@ -1,36 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 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
|
-
}
|
|
34
6
|
/**
|
|
35
7
|
* Currency symbols used for display formatting.
|
|
36
8
|
* Prefix currencies (USD, EUR, GBP, CAD, AUD) show symbol before the amount.
|
|
@@ -45,51 +17,24 @@ export declare const CURRENCY_NAMES: Record<SupportedCurrency, string>;
|
|
|
45
17
|
* Currency store state interface.
|
|
46
18
|
* @property selectedCurrency - The currency currently selected for display
|
|
47
19
|
* @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
|
|
50
20
|
* @property setSelectedCurrency - Update the selected display currency
|
|
51
21
|
* @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)
|
|
57
22
|
*/
|
|
58
23
|
interface CurrencyState {
|
|
59
|
-
selectedCurrency:
|
|
60
|
-
baseCurrency:
|
|
61
|
-
|
|
62
|
-
|
|
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[];
|
|
24
|
+
selectedCurrency: SupportedCurrency;
|
|
25
|
+
baseCurrency: SupportedCurrency;
|
|
26
|
+
setSelectedCurrency: (currency: SupportedCurrency) => void;
|
|
27
|
+
setBaseCurrency: (currency: SupportedCurrency) => void;
|
|
70
28
|
}
|
|
71
29
|
/**
|
|
72
30
|
* Zustand store for managing currency selection and conversion.
|
|
73
31
|
* Persists user's selected currency preference in localStorage.
|
|
74
|
-
* Supports dynamic currency registration and custom exchange rates.
|
|
75
32
|
*
|
|
76
33
|
* @example
|
|
77
34
|
* ```tsx
|
|
78
|
-
* const { selectedCurrency, setSelectedCurrency
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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');
|
|
35
|
+
* const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
|
|
36
|
+
* // Change display currency to USD
|
|
37
|
+
* setSelectedCurrency('USD');
|
|
93
38
|
* ```
|
|
94
39
|
*/
|
|
95
40
|
export declare const useCurrencyStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<CurrencyState>, "persist"> & {
|
|
@@ -103,24 +48,4 @@ export declare const useCurrencyStore: import("zustand").UseBoundStore<Omit<impo
|
|
|
103
48
|
getOptions: () => Partial<import("zustand/middleware").PersistOptions<CurrencyState, CurrencyState>>;
|
|
104
49
|
};
|
|
105
50
|
}>;
|
|
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;
|
|
126
51
|
export {};
|
|
@@ -35,158 +35,20 @@ export const CURRENCY_NAMES = {
|
|
|
35
35
|
/**
|
|
36
36
|
* Zustand store for managing currency selection and conversion.
|
|
37
37
|
* Persists user's selected currency preference in localStorage.
|
|
38
|
-
* Supports dynamic currency registration and custom exchange rates.
|
|
39
38
|
*
|
|
40
39
|
* @example
|
|
41
40
|
* ```tsx
|
|
42
|
-
* const { selectedCurrency, setSelectedCurrency
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* addCurrency({
|
|
46
|
-
* code: "BTC",
|
|
47
|
-
* symbol: "₿",
|
|
48
|
-
* name: "Bitcoin",
|
|
49
|
-
* showSubscripts: true,
|
|
50
|
-
* });
|
|
51
|
-
*
|
|
52
|
-
* // Set exchange rate: 1 BTC = 50000 USD
|
|
53
|
-
* setExchangeRate("BTC", "USD", 50000);
|
|
54
|
-
*
|
|
55
|
-
* // Change display currency
|
|
56
|
-
* setSelectedCurrency('BTC');
|
|
41
|
+
* const { selectedCurrency, setSelectedCurrency } = useCurrencyStore();
|
|
42
|
+
* // Change display currency to USD
|
|
43
|
+
* setSelectedCurrency('USD');
|
|
57
44
|
* ```
|
|
58
45
|
*/
|
|
59
|
-
export const useCurrencyStore = create()(persist(
|
|
46
|
+
export const useCurrencyStore = create()(persist(set => ({
|
|
60
47
|
selectedCurrency: "B3",
|
|
61
48
|
baseCurrency: "B3",
|
|
62
|
-
customCurrencies: {},
|
|
63
|
-
customExchangeRates: {},
|
|
64
49
|
setSelectedCurrency: currency => set({ selectedCurrency: currency }),
|
|
65
50
|
setBaseCurrency: currency => set({ baseCurrency: currency }),
|
|
66
|
-
addCurrency: metadata => {
|
|
67
|
-
set(state => ({
|
|
68
|
-
customCurrencies: {
|
|
69
|
-
...state.customCurrencies,
|
|
70
|
-
[metadata.code]: metadata,
|
|
71
|
-
},
|
|
72
|
-
}));
|
|
73
|
-
},
|
|
74
|
-
removeCurrency: code => {
|
|
75
|
-
set(state => {
|
|
76
|
-
// Remove the currency
|
|
77
|
-
const { [code]: _removed, ...remaining } = state.customCurrencies;
|
|
78
|
-
// Remove all exchange rates involving this currency
|
|
79
|
-
const filteredRates = {};
|
|
80
|
-
for (const [key, rate] of Object.entries(state.customExchangeRates)) {
|
|
81
|
-
// Key format is "FROM-TO", skip if either matches the removed code
|
|
82
|
-
const [from, to] = key.split("-");
|
|
83
|
-
if (from !== code && to !== code) {
|
|
84
|
-
filteredRates[key] = rate;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
customCurrencies: remaining,
|
|
89
|
-
customExchangeRates: filteredRates,
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
|
-
},
|
|
93
|
-
setExchangeRate: (from, to, rate) => {
|
|
94
|
-
set(state => {
|
|
95
|
-
const key = `${from}-${to}`;
|
|
96
|
-
const inverseKey = `${to}-${from}`;
|
|
97
|
-
// Only set inverse rate if rate is not 0 (to avoid Infinity)
|
|
98
|
-
const newRates = {
|
|
99
|
-
...state.customExchangeRates,
|
|
100
|
-
[key]: rate,
|
|
101
|
-
};
|
|
102
|
-
if (rate !== 0) {
|
|
103
|
-
newRates[inverseKey] = 1 / rate;
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
customExchangeRates: newRates,
|
|
107
|
-
};
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
getExchangeRate: (from, to) => {
|
|
111
|
-
const key = `${from}-${to}`;
|
|
112
|
-
return get().customExchangeRates[key];
|
|
113
|
-
},
|
|
114
|
-
getAllCurrencies: () => {
|
|
115
|
-
const builtIn = Object.keys(CURRENCY_SYMBOLS);
|
|
116
|
-
const custom = Object.keys(get().customCurrencies);
|
|
117
|
-
return [...builtIn, ...custom];
|
|
118
|
-
},
|
|
119
51
|
}), {
|
|
120
52
|
name: "currency-storage",
|
|
121
|
-
version:
|
|
53
|
+
version: 2,
|
|
122
54
|
}));
|
|
123
|
-
/**
|
|
124
|
-
* Get the symbol for any currency (built-in or custom).
|
|
125
|
-
*/
|
|
126
|
-
export function getCurrencySymbol(currency) {
|
|
127
|
-
// Check built-in currencies first
|
|
128
|
-
if (currency in CURRENCY_SYMBOLS) {
|
|
129
|
-
return CURRENCY_SYMBOLS[currency];
|
|
130
|
-
}
|
|
131
|
-
// Check custom currencies
|
|
132
|
-
const customCurrencies = useCurrencyStore.getState().customCurrencies;
|
|
133
|
-
const customCurrency = customCurrencies[currency];
|
|
134
|
-
if (customCurrency) {
|
|
135
|
-
return customCurrency.symbol;
|
|
136
|
-
}
|
|
137
|
-
// Fallback to currency code
|
|
138
|
-
return currency;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Get the name for any currency (built-in or custom).
|
|
142
|
-
*/
|
|
143
|
-
export function getCurrencyName(currency) {
|
|
144
|
-
// Check built-in currencies first
|
|
145
|
-
if (currency in CURRENCY_NAMES) {
|
|
146
|
-
return CURRENCY_NAMES[currency];
|
|
147
|
-
}
|
|
148
|
-
// Check custom currencies
|
|
149
|
-
const customCurrencies = useCurrencyStore.getState().customCurrencies;
|
|
150
|
-
const customCurrency = customCurrencies[currency];
|
|
151
|
-
if (customCurrency) {
|
|
152
|
-
return customCurrency.name;
|
|
153
|
-
}
|
|
154
|
-
// Fallback to currency code
|
|
155
|
-
return currency;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Get metadata for a custom currency.
|
|
159
|
-
*/
|
|
160
|
-
export function getCurrencyMetadata(currency) {
|
|
161
|
-
const customCurrencies = useCurrencyStore.getState().customCurrencies;
|
|
162
|
-
return customCurrencies[currency];
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Get the number of decimal places for a currency (for converting from smallest unit).
|
|
166
|
-
* Used when parsing amounts from wei/smallest unit format.
|
|
167
|
-
*
|
|
168
|
-
* @param currency - Currency code
|
|
169
|
-
* @returns Number of decimal places (e.g., 18 for ETH/wei, 2 for USD cents, 0 for JPY)
|
|
170
|
-
*/
|
|
171
|
-
export function getCurrencyDecimalPlaces(currency) {
|
|
172
|
-
// Check custom currencies first
|
|
173
|
-
const customCurrencies = useCurrencyStore.getState().customCurrencies;
|
|
174
|
-
const customMetadata = customCurrencies[currency];
|
|
175
|
-
if (customMetadata?.decimals !== undefined) {
|
|
176
|
-
return customMetadata.decimals;
|
|
177
|
-
}
|
|
178
|
-
// Built-in currencies with 18 decimals (wei-like)
|
|
179
|
-
if (currency === "WIN" || currency === "ETH" || currency === "SOL" || currency === "B3") {
|
|
180
|
-
return 18;
|
|
181
|
-
}
|
|
182
|
-
// Fiat currencies with cent-like decimals
|
|
183
|
-
if (currency === "USD" || currency === "EUR" || currency === "GBP" || currency === "CAD" || currency === "AUD") {
|
|
184
|
-
return 2;
|
|
185
|
-
}
|
|
186
|
-
// Currencies without fractional units
|
|
187
|
-
if (currency === "JPY" || currency === "KRW") {
|
|
188
|
-
return 0;
|
|
189
|
-
}
|
|
190
|
-
// Default to 18 decimals (wei-like)
|
|
191
|
-
return 18;
|
|
192
|
-
}
|