@one_deploy/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.turbo/turbo-build.log +0 -0
  2. package/.turbo/turbo-type-check.log +0 -0
  3. package/dist/config/index.d.mts +74 -0
  4. package/dist/config/index.d.ts +74 -0
  5. package/dist/config/index.js +242 -0
  6. package/dist/config/index.js.map +1 -0
  7. package/dist/config/index.mjs +224 -0
  8. package/dist/config/index.mjs.map +1 -0
  9. package/dist/engine-5ndtBaCr.d.ts +1039 -0
  10. package/dist/engine-CrlhH0nw.d.mts +1039 -0
  11. package/dist/hooks/index.d.mts +56 -0
  12. package/dist/hooks/index.d.ts +56 -0
  13. package/dist/hooks/index.js +1360 -0
  14. package/dist/hooks/index.js.map +1 -0
  15. package/dist/hooks/index.mjs +1356 -0
  16. package/dist/hooks/index.mjs.map +1 -0
  17. package/dist/index.d.mts +356 -0
  18. package/dist/index.d.ts +356 -0
  19. package/dist/index.js +5068 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/index.mjs +4949 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/price-CgqXPnT3.d.ts +13 -0
  24. package/dist/price-ClbLHHjv.d.mts +13 -0
  25. package/dist/providers/index.d.mts +121 -0
  26. package/dist/providers/index.d.ts +121 -0
  27. package/dist/providers/index.js +1642 -0
  28. package/dist/providers/index.js.map +1 -0
  29. package/dist/providers/index.mjs +1600 -0
  30. package/dist/providers/index.mjs.map +1 -0
  31. package/dist/react-native.d.mts +120 -0
  32. package/dist/react-native.d.ts +120 -0
  33. package/dist/react-native.js +1792 -0
  34. package/dist/react-native.js.map +1 -0
  35. package/dist/react-native.mjs +1755 -0
  36. package/dist/react-native.mjs.map +1 -0
  37. package/dist/services/index.d.mts +85 -0
  38. package/dist/services/index.d.ts +85 -0
  39. package/dist/services/index.js +1466 -0
  40. package/dist/services/index.js.map +1 -0
  41. package/dist/services/index.mjs +1458 -0
  42. package/dist/services/index.mjs.map +1 -0
  43. package/dist/types/index.d.mts +759 -0
  44. package/dist/types/index.d.ts +759 -0
  45. package/dist/types/index.js +4 -0
  46. package/dist/types/index.js.map +1 -0
  47. package/dist/types/index.mjs +3 -0
  48. package/dist/types/index.mjs.map +1 -0
  49. package/dist/utils/index.d.mts +36 -0
  50. package/dist/utils/index.d.ts +36 -0
  51. package/dist/utils/index.js +164 -0
  52. package/dist/utils/index.js.map +1 -0
  53. package/dist/utils/index.mjs +142 -0
  54. package/dist/utils/index.mjs.map +1 -0
  55. package/package.json +101 -0
  56. package/src/components/OneConnectButton.tsx +143 -0
  57. package/src/components/OneNFTGallery.tsx +324 -0
  58. package/src/components/OneOfframpWidget.tsx +660 -0
  59. package/src/components/OneOnrampWidget.tsx +596 -0
  60. package/src/components/OnePayWidget.tsx +160 -0
  61. package/src/components/OneReceiveWidget.tsx +272 -0
  62. package/src/components/OneSendWidget.tsx +248 -0
  63. package/src/components/OneSwapWidget.tsx +715 -0
  64. package/src/components/OneTransactionButton.tsx +150 -0
  65. package/src/components/OneWalletBalance.tsx +354 -0
  66. package/src/components/index.ts +24 -0
  67. package/src/config/index.ts +299 -0
  68. package/src/hooks/index.ts +2 -0
  69. package/src/hooks/useTokenPrice.ts +162 -0
  70. package/src/hooks/useWalletBalance.ts +98 -0
  71. package/src/index.ts +193 -0
  72. package/src/providers/OneProvider.tsx +452 -0
  73. package/src/providers/ThirdwebProvider.tsx +203 -0
  74. package/src/providers/index.ts +26 -0
  75. package/src/react-native.ts +378 -0
  76. package/src/services/engine.ts +1854 -0
  77. package/src/services/index.ts +30 -0
  78. package/src/services/price.ts +164 -0
  79. package/src/services/supabase.ts +180 -0
  80. package/src/types/index.ts +887 -0
  81. package/src/utils/index.ts +200 -0
  82. package/tsconfig.json +22 -0
  83. package/tsup.config.ts +25 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * ONE SDK Configuration
3
+ *
4
+ * Chain data is fetched from ONE Engine API - no duplicates needed here.
5
+ * Engine is the single source of truth for 200+ EVM chains.
6
+ */
7
+
8
+ import type { ChainConfig } from '../types';
9
+
10
+ // ===== SDK Configuration =====
11
+ export interface OneConfig {
12
+ // ONE Engine (required)
13
+ oneEngineUrl: string;
14
+ oneClientId: string;
15
+ oneSecretKey?: string; // Only for backend usage
16
+
17
+ // Optional: Direct Supabase access (for realtime subscriptions)
18
+ supabaseUrl?: string;
19
+ supabaseAnonKey?: string;
20
+
21
+ // Deprecated: Use ONE Engine instead
22
+ thirdwebClientId?: string;
23
+ }
24
+
25
+ let config: OneConfig | null = null;
26
+
27
+ export function initOneSDK(options: OneConfig): void {
28
+ if (!options.oneEngineUrl) {
29
+ throw new Error('oneEngineUrl is required');
30
+ }
31
+ if (!options.oneClientId) {
32
+ throw new Error('oneClientId is required');
33
+ }
34
+ config = options;
35
+ }
36
+
37
+ export function getConfig(): OneConfig {
38
+ if (!config) {
39
+ throw new Error('ONE SDK not initialized. Call initOneSDK() first.');
40
+ }
41
+ return config;
42
+ }
43
+
44
+ export function isInitialized(): boolean {
45
+ return config !== null;
46
+ }
47
+
48
+ export function getEngineUrl(): string {
49
+ return config?.oneEngineUrl || process.env.NEXT_PUBLIC_ONE_ENGINE_URL || 'http://localhost:4000/api';
50
+ }
51
+
52
+ // ===== Chain Data (Fetched from Engine) =====
53
+
54
+ // Cache for chain data
55
+ let chainsCache: ChainConfig[] | null = null;
56
+ let chainsCacheTimestamp = 0;
57
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
58
+
59
+ /**
60
+ * Fetch chains from ONE Engine API
61
+ * Engine is the source of truth for 200+ EVM chains
62
+ */
63
+ export async function fetchChains(options?: {
64
+ category?: 'mainnet' | 'l2' | 'testnet' | 'gaming' | 'recommended';
65
+ smartWallet?: boolean;
66
+ limit?: number;
67
+ }): Promise<ChainConfig[]> {
68
+ const engineUrl = getEngineUrl();
69
+ const params = new URLSearchParams();
70
+
71
+ if (options?.category) params.set('category', options.category);
72
+ if (options?.smartWallet) params.set('smartWallet', 'true');
73
+ if (options?.limit) params.set('limit', options.limit.toString());
74
+
75
+ const response = await fetch(`${engineUrl}/v1/chains?${params}`);
76
+ const data = await response.json();
77
+
78
+ if (!data.success) {
79
+ throw new Error(data.error?.message || 'Failed to fetch chains');
80
+ }
81
+
82
+ // Transform Engine response to SDK ChainConfig format
83
+ return data.data.chains.map((chain: any) => ({
84
+ id: chain.id,
85
+ name: chain.name,
86
+ shortName: chain.shortName || chain.slug || chain.name.toLowerCase(),
87
+ icon: getChainIcon(chain.id),
88
+ nativeCurrency: chain.nativeCurrency,
89
+ rpcUrls: Array.isArray(chain.rpc) ? chain.rpc : [chain.rpc],
90
+ blockExplorerUrls: chain.blockExplorer ? [chain.blockExplorer] : [],
91
+ testnet: chain.testnet,
92
+ }));
93
+ }
94
+
95
+ /**
96
+ * Get all supported chains (cached)
97
+ */
98
+ export async function getChains(): Promise<ChainConfig[]> {
99
+ const now = Date.now();
100
+ if (chainsCache && (now - chainsCacheTimestamp) < CACHE_TTL) {
101
+ return chainsCache;
102
+ }
103
+
104
+ chainsCache = await fetchChains({ limit: 200 });
105
+ chainsCacheTimestamp = now;
106
+ return chainsCache;
107
+ }
108
+
109
+ /**
110
+ * Get chain by ID
111
+ */
112
+ export async function getChainById(chainId: number): Promise<ChainConfig | undefined> {
113
+ const chains = await getChains();
114
+ return chains.find(c => c.id === chainId);
115
+ }
116
+
117
+ /**
118
+ * Get chain by name/shortName
119
+ */
120
+ export async function getChainByName(name: string): Promise<ChainConfig | undefined> {
121
+ const chains = await getChains();
122
+ const lowerName = name.toLowerCase();
123
+ return chains.find(
124
+ c => c.name.toLowerCase() === lowerName || c.shortName.toLowerCase() === lowerName
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Get recommended chains for UI display
130
+ */
131
+ export async function getRecommendedChains(): Promise<ChainConfig[]> {
132
+ return fetchChains({ category: 'recommended', limit: 10 });
133
+ }
134
+
135
+ /**
136
+ * Get chains with smart wallet support
137
+ */
138
+ export async function getSmartWalletChains(): Promise<ChainConfig[]> {
139
+ return fetchChains({ smartWallet: true, limit: 50 });
140
+ }
141
+
142
+ // ===== Default Chain Constants =====
143
+ // These are commonly used chain IDs, but full data should be fetched from Engine
144
+
145
+ export const CHAIN_IDS = {
146
+ ETHEREUM: 1,
147
+ POLYGON: 137,
148
+ BSC: 56,
149
+ ARBITRUM: 42161,
150
+ OPTIMISM: 10,
151
+ BASE: 8453,
152
+ AVALANCHE: 43114,
153
+ ZKSYNC: 324,
154
+ LINEA: 59144,
155
+ SCROLL: 534352,
156
+ BLAST: 81457,
157
+ // Testnets
158
+ SEPOLIA: 11155111,
159
+ BASE_SEPOLIA: 84532,
160
+ ARBITRUM_SEPOLIA: 421614,
161
+ } as const;
162
+
163
+ export const DEFAULT_CHAIN_ID = CHAIN_IDS.BASE;
164
+
165
+ // ===== Chain Icons (Simple emoji fallback) =====
166
+ const CHAIN_ICONS: Record<number, string> = {
167
+ 1: '\u229F', // Ethereum
168
+ 137: '\uD83D\uDFE3', // Polygon
169
+ 56: '\uD83D\uDFE1', // BSC
170
+ 42161: '\uD83D\uDD35', // Arbitrum
171
+ 10: '\uD83D\uDD34', // Optimism
172
+ 8453: '\uD83D\uDD37', // Base
173
+ 43114: '\uD83D\uDD3A', // Avalanche
174
+ 324: '\u26A1', // zkSync
175
+ 59144: '\uD83D\uDD39', // Linea
176
+ 534352: '\uD83D\uDCDC', // Scroll
177
+ };
178
+
179
+ function getChainIcon(chainId: number): string {
180
+ return CHAIN_ICONS[chainId] || '\uD83D\uDD17';
181
+ }
182
+
183
+ // ===== Token Names Mapping =====
184
+ // Common token symbols to human-readable names
185
+ export const TOKEN_NAMES: Record<string, string> = {
186
+ ETH: 'Ethereum',
187
+ BTC: 'Bitcoin',
188
+ BNB: 'BNB',
189
+ MATIC: 'Polygon',
190
+ POL: 'Polygon',
191
+ AVAX: 'Avalanche',
192
+ USDT: 'Tether',
193
+ USDC: 'USD Coin',
194
+ DAI: 'Dai',
195
+ WBTC: 'Wrapped Bitcoin',
196
+ WETH: 'Wrapped Ether',
197
+ ARB: 'Arbitrum',
198
+ OP: 'Optimism',
199
+ LINK: 'Chainlink',
200
+ UNI: 'Uniswap',
201
+ AAVE: 'Aave',
202
+ CRV: 'Curve',
203
+ MKR: 'Maker',
204
+ SNX: 'Synthetix',
205
+ COMP: 'Compound',
206
+ SUSHI: 'SushiSwap',
207
+ YFI: 'Yearn Finance',
208
+ SOL: 'Solana',
209
+ DOT: 'Polkadot',
210
+ ATOM: 'Cosmos',
211
+ NEAR: 'Near Protocol',
212
+ };
213
+
214
+ // ===== CoinGecko ID Mapping =====
215
+ // For price lookups via CoinGecko API
216
+ export const COINGECKO_IDS: Record<string, string> = {
217
+ ETH: 'ethereum',
218
+ BTC: 'bitcoin',
219
+ BNB: 'binancecoin',
220
+ MATIC: 'matic-network',
221
+ POL: 'matic-network',
222
+ AVAX: 'avalanche-2',
223
+ USDT: 'tether',
224
+ USDC: 'usd-coin',
225
+ DAI: 'dai',
226
+ WBTC: 'wrapped-bitcoin',
227
+ WETH: 'weth',
228
+ ARB: 'arbitrum',
229
+ OP: 'optimism',
230
+ LINK: 'chainlink',
231
+ UNI: 'uniswap',
232
+ AAVE: 'aave',
233
+ SOL: 'solana',
234
+ };
235
+
236
+ // ===== Backwards Compatibility =====
237
+ // @deprecated - Use getChains() or fetch from Engine API
238
+ // These static configs are kept for backwards compatibility only
239
+
240
+ export const CHAIN_CONFIGS: Record<string, ChainConfig> = {
241
+ ethereum: {
242
+ id: 1,
243
+ name: 'Ethereum',
244
+ shortName: 'ETH',
245
+ icon: '\u229F',
246
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
247
+ rpcUrls: ['https://ethereum.rpc.thirdweb.com'],
248
+ blockExplorerUrls: ['https://etherscan.io'],
249
+ testnet: false,
250
+ },
251
+ polygon: {
252
+ id: 137,
253
+ name: 'Polygon',
254
+ shortName: 'MATIC',
255
+ icon: '\uD83D\uDFE3',
256
+ nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
257
+ rpcUrls: ['https://polygon.rpc.thirdweb.com'],
258
+ blockExplorerUrls: ['https://polygonscan.com'],
259
+ testnet: false,
260
+ },
261
+ base: {
262
+ id: 8453,
263
+ name: 'Base',
264
+ shortName: 'BASE',
265
+ icon: '\uD83D\uDD37',
266
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
267
+ rpcUrls: ['https://base.rpc.thirdweb.com'],
268
+ blockExplorerUrls: ['https://basescan.org'],
269
+ testnet: false,
270
+ },
271
+ arbitrum: {
272
+ id: 42161,
273
+ name: 'Arbitrum One',
274
+ shortName: 'ARB',
275
+ icon: '\uD83D\uDD35',
276
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
277
+ rpcUrls: ['https://arbitrum.rpc.thirdweb.com'],
278
+ blockExplorerUrls: ['https://arbiscan.io'],
279
+ testnet: false,
280
+ },
281
+ optimism: {
282
+ id: 10,
283
+ name: 'Optimism',
284
+ shortName: 'OP',
285
+ icon: '\uD83D\uDD34',
286
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
287
+ rpcUrls: ['https://optimism.rpc.thirdweb.com'],
288
+ blockExplorerUrls: ['https://optimistic.etherscan.io'],
289
+ testnet: false,
290
+ },
291
+ };
292
+
293
+ // @deprecated - Use getChains() to get all supported chains
294
+ export const SUPPORTED_CHAINS = Object.keys(CHAIN_CONFIGS);
295
+
296
+ // @deprecated - Use getChainByName() instead
297
+ export function getChainConfig(chain: string): ChainConfig | undefined {
298
+ return CHAIN_CONFIGS[chain.toLowerCase()];
299
+ }
@@ -0,0 +1,2 @@
1
+ export { useWalletBalance } from './useWalletBalance';
2
+ export { useTokenPrice, useTokenPrices } from './useTokenPrice';
@@ -0,0 +1,162 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { createOneEngineClient } from '../services/engine';
3
+ import type { TokenPrice } from '../types';
4
+
5
+ interface UseTokenPriceOptions {
6
+ autoRefresh?: boolean;
7
+ refreshInterval?: number;
8
+ engineUrl?: string;
9
+ clientId?: string;
10
+ }
11
+
12
+ interface UseTokenPriceReturn {
13
+ price: TokenPrice | null;
14
+ isLoading: boolean;
15
+ error: string | null;
16
+ refetch: () => Promise<void>;
17
+ }
18
+
19
+ /**
20
+ * Hook for fetching token price via ONE Engine
21
+ *
22
+ * Note: If using OneProvider, prefer useOneEngine().getTokenPrices() instead.
23
+ * This hook is for standalone usage outside of OneProvider.
24
+ */
25
+ export function useTokenPrice(
26
+ symbol: string,
27
+ options: UseTokenPriceOptions = {}
28
+ ): UseTokenPriceReturn {
29
+ const { autoRefresh = false, refreshInterval = 30000, engineUrl, clientId } = options;
30
+
31
+ const [price, setPrice] = useState<TokenPrice | null>(null);
32
+ const [isLoading, setIsLoading] = useState(false);
33
+ const [error, setError] = useState<string | null>(null);
34
+
35
+ const engineRef = useRef(createOneEngineClient({
36
+ baseUrl: engineUrl,
37
+ clientId: clientId,
38
+ }));
39
+
40
+ const fetchPrice = useCallback(async () => {
41
+ if (!symbol) return;
42
+
43
+ setIsLoading(true);
44
+ setError(null);
45
+
46
+ try {
47
+ const result = await engineRef.current.getTokenPrices([symbol]);
48
+ if (result.success && result.data && result.data[symbol]) {
49
+ const priceData = result.data[symbol];
50
+ setPrice({
51
+ symbol,
52
+ price: priceData.price,
53
+ change24h: priceData.change24h,
54
+ changePercent24h: priceData.change24h, // Same as change24h for percentage
55
+ marketCap: priceData.marketCap,
56
+ volume24h: 0, // Not provided by engine
57
+ updatedAt: new Date().toISOString(),
58
+ });
59
+ } else {
60
+ setError(result.error?.message || 'Failed to fetch price');
61
+ }
62
+ } catch (err) {
63
+ setError(err instanceof Error ? err.message : 'Failed to fetch price');
64
+ } finally {
65
+ setIsLoading(false);
66
+ }
67
+ }, [symbol]);
68
+
69
+ useEffect(() => {
70
+ fetchPrice();
71
+ }, [fetchPrice]);
72
+
73
+ useEffect(() => {
74
+ if (!autoRefresh) return;
75
+
76
+ const interval = setInterval(fetchPrice, refreshInterval);
77
+ return () => clearInterval(interval);
78
+ }, [autoRefresh, refreshInterval, fetchPrice]);
79
+
80
+ return {
81
+ price,
82
+ isLoading,
83
+ error,
84
+ refetch: fetchPrice,
85
+ };
86
+ }
87
+
88
+ // Hook for multiple token prices
89
+ interface UseTokenPricesReturn {
90
+ prices: Record<string, TokenPrice>;
91
+ isLoading: boolean;
92
+ error: string | null;
93
+ refetch: () => Promise<void>;
94
+ }
95
+
96
+ export function useTokenPrices(
97
+ symbols: string[],
98
+ options: UseTokenPriceOptions = {}
99
+ ): UseTokenPricesReturn {
100
+ const { autoRefresh = false, refreshInterval = 30000, engineUrl, clientId } = options;
101
+
102
+ const [prices, setPrices] = useState<Record<string, TokenPrice>>({});
103
+ const [isLoading, setIsLoading] = useState(false);
104
+ const [error, setError] = useState<string | null>(null);
105
+
106
+ const engineRef = useRef(createOneEngineClient({
107
+ baseUrl: engineUrl,
108
+ clientId: clientId,
109
+ }));
110
+
111
+ const fetchPrices = useCallback(async () => {
112
+ if (symbols.length === 0) return;
113
+
114
+ setIsLoading(true);
115
+ setError(null);
116
+
117
+ try {
118
+ const result = await engineRef.current.getTokenPrices(symbols);
119
+ if (result.success && result.data) {
120
+ const priceMap: Record<string, TokenPrice> = {};
121
+ for (const sym of symbols) {
122
+ if (result.data[sym]) {
123
+ priceMap[sym] = {
124
+ symbol: sym,
125
+ price: result.data[sym].price,
126
+ change24h: result.data[sym].change24h,
127
+ changePercent24h: result.data[sym].change24h,
128
+ marketCap: result.data[sym].marketCap,
129
+ volume24h: 0,
130
+ updatedAt: new Date().toISOString(),
131
+ };
132
+ }
133
+ }
134
+ setPrices(priceMap);
135
+ } else {
136
+ setError(result.error?.message || 'Failed to fetch prices');
137
+ }
138
+ } catch (err) {
139
+ setError(err instanceof Error ? err.message : 'Failed to fetch prices');
140
+ } finally {
141
+ setIsLoading(false);
142
+ }
143
+ }, [symbols]);
144
+
145
+ useEffect(() => {
146
+ fetchPrices();
147
+ }, [fetchPrices]);
148
+
149
+ useEffect(() => {
150
+ if (!autoRefresh) return;
151
+
152
+ const interval = setInterval(fetchPrices, refreshInterval);
153
+ return () => clearInterval(interval);
154
+ }, [autoRefresh, refreshInterval, fetchPrices]);
155
+
156
+ return {
157
+ prices,
158
+ isLoading,
159
+ error,
160
+ refetch: fetchPrices,
161
+ };
162
+ }
@@ -0,0 +1,98 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { createOneEngineClient, type EngineWalletBalance } from '../services/engine';
3
+ import type { Token } from '../types';
4
+
5
+ interface UseWalletBalanceOptions {
6
+ chains?: number[];
7
+ autoRefresh?: boolean;
8
+ refreshInterval?: number; // in milliseconds
9
+ engineUrl?: string;
10
+ clientId?: string;
11
+ }
12
+
13
+ interface UseWalletBalanceReturn {
14
+ balance: EngineWalletBalance | null;
15
+ tokens: Token[];
16
+ totalUsd: number;
17
+ change24h: number;
18
+ changePercent24h: number;
19
+ isLoading: boolean;
20
+ error: string | null;
21
+ refetch: () => Promise<void>;
22
+ }
23
+
24
+ /**
25
+ * Hook for fetching wallet balance via ONE Engine
26
+ *
27
+ * Note: If using OneProvider, prefer useOneWallet() instead for better integration.
28
+ * This hook is for standalone usage outside of OneProvider.
29
+ */
30
+ export function useWalletBalance(
31
+ walletAddress: string | null,
32
+ options: UseWalletBalanceOptions = {}
33
+ ): UseWalletBalanceReturn {
34
+ const {
35
+ chains,
36
+ autoRefresh = false,
37
+ refreshInterval = 60000, // 1 minute
38
+ engineUrl,
39
+ clientId,
40
+ } = options;
41
+
42
+ const [balance, setBalance] = useState<EngineWalletBalance | null>(null);
43
+ const [isLoading, setIsLoading] = useState(false);
44
+ const [error, setError] = useState<string | null>(null);
45
+
46
+ // Create engine client once
47
+ const engineRef = useRef(createOneEngineClient({
48
+ baseUrl: engineUrl,
49
+ clientId: clientId,
50
+ }));
51
+
52
+ const fetchBalance = useCallback(async () => {
53
+ if (!walletAddress) {
54
+ setBalance(null);
55
+ return;
56
+ }
57
+
58
+ setIsLoading(true);
59
+ setError(null);
60
+
61
+ try {
62
+ const result = await engineRef.current.getWalletBalance(walletAddress, chains);
63
+ if (result.success && result.data) {
64
+ setBalance(result.data);
65
+ } else {
66
+ setError(result.error?.message || 'Failed to fetch balance');
67
+ }
68
+ } catch (err) {
69
+ setError(err instanceof Error ? err.message : 'Failed to fetch balance');
70
+ } finally {
71
+ setIsLoading(false);
72
+ }
73
+ }, [walletAddress, chains]);
74
+
75
+ // Initial fetch
76
+ useEffect(() => {
77
+ fetchBalance();
78
+ }, [fetchBalance]);
79
+
80
+ // Auto-refresh
81
+ useEffect(() => {
82
+ if (!autoRefresh || !walletAddress) return;
83
+
84
+ const interval = setInterval(fetchBalance, refreshInterval);
85
+ return () => clearInterval(interval);
86
+ }, [autoRefresh, refreshInterval, fetchBalance, walletAddress]);
87
+
88
+ return {
89
+ balance,
90
+ tokens: balance?.tokens || [],
91
+ totalUsd: balance?.totalUsd || 0,
92
+ change24h: balance?.change24h || 0,
93
+ changePercent24h: balance?.changePercent24h || 0,
94
+ isLoading,
95
+ error,
96
+ refetch: fetchBalance,
97
+ };
98
+ }