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