@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,203 @@
1
+ 'use client';
2
+
3
+ import React, { type ReactNode, useMemo, useEffect, useState } from 'react';
4
+ import { createThirdwebClient, type ThirdwebClient } from 'thirdweb';
5
+ import { ThirdwebProvider as BaseThirdwebProvider } from 'thirdweb/react';
6
+ import { inAppWallet, smartWallet } from 'thirdweb/wallets';
7
+ import type { Chain } from 'thirdweb/chains';
8
+ import { base, ethereum, polygon, arbitrum, optimism } from 'thirdweb/chains';
9
+
10
+ // ===== Types =====
11
+
12
+ export interface OneThirdwebConfig {
13
+ // Engine URL to fetch thirdweb config (clientId managed by Engine)
14
+ engineUrl?: string;
15
+ // Or use pre-configured ONE ecosystem clientId (fetched from Engine)
16
+ // Users do NOT need to provide their own clientId
17
+ appName?: string;
18
+ appIcon?: string;
19
+ defaultChain?: Chain;
20
+ supportedChains?: Chain[];
21
+ sponsorGas?: boolean;
22
+ authOptions?: {
23
+ email?: boolean;
24
+ phone?: boolean;
25
+ google?: boolean;
26
+ apple?: boolean;
27
+ facebook?: boolean;
28
+ discord?: boolean;
29
+ passkey?: boolean;
30
+ guest?: boolean;
31
+ };
32
+ }
33
+
34
+ export interface OneThirdwebProviderProps {
35
+ children: ReactNode;
36
+ config?: OneThirdwebConfig;
37
+ }
38
+
39
+ // ===== Default Configuration =====
40
+
41
+ const DEFAULT_CHAINS: Chain[] = [base, ethereum, polygon, arbitrum, optimism];
42
+
43
+ const DEFAULT_AUTH_OPTIONS = {
44
+ email: true,
45
+ phone: false,
46
+ google: true,
47
+ apple: true,
48
+ facebook: false,
49
+ discord: false,
50
+ passkey: true,
51
+ guest: false,
52
+ };
53
+
54
+ // ===== Context for Client Access =====
55
+
56
+ const ThirdwebClientContext = React.createContext<ThirdwebClient | null>(null);
57
+
58
+ export function useThirdwebClient(): ThirdwebClient {
59
+ const client = React.useContext(ThirdwebClientContext);
60
+ if (!client) {
61
+ throw new Error('useThirdwebClient must be used within OneThirdwebProvider');
62
+ }
63
+ return client;
64
+ }
65
+
66
+ // ===== Create Wallets Configuration =====
67
+
68
+ function createWalletConfig(config: OneThirdwebConfig) {
69
+ const authOptions = { ...DEFAULT_AUTH_OPTIONS, ...config.authOptions };
70
+
71
+ // Build auth options array
72
+ const authMethods: string[] = [];
73
+ if (authOptions.google) authMethods.push('google');
74
+ if (authOptions.apple) authMethods.push('apple');
75
+ if (authOptions.facebook) authMethods.push('facebook');
76
+ if (authOptions.discord) authMethods.push('discord');
77
+ if (authOptions.passkey) authMethods.push('passkey');
78
+
79
+ // Create in-app wallet with email and social logins
80
+ const inApp = inAppWallet({
81
+ auth: {
82
+ options: authMethods as any[],
83
+ },
84
+ metadata: config.appName ? {
85
+ name: config.appName,
86
+ image: config.appIcon ? { src: config.appIcon, width: 100, height: 100 } : undefined,
87
+ } : undefined,
88
+ });
89
+
90
+ // If gas sponsorship is enabled, wrap in smart wallet
91
+ if (config.sponsorGas) {
92
+ const chain = config.defaultChain || base;
93
+ return [
94
+ smartWallet({
95
+ chain,
96
+ sponsorGas: true,
97
+ }),
98
+ ];
99
+ }
100
+
101
+ return [inApp];
102
+ }
103
+
104
+ // ===== Default Engine URL =====
105
+ const DEFAULT_ENGINE_URL = process.env.NEXT_PUBLIC_ONE_ENGINE_URL || '/api';
106
+
107
+ // ===== Provider Component =====
108
+
109
+ export function OneThirdwebProvider({
110
+ children,
111
+ config = {},
112
+ }: OneThirdwebProviderProps) {
113
+ const [clientId, setClientId] = useState<string | null>(null);
114
+ const [isLoading, setIsLoading] = useState(true);
115
+ const [error, setError] = useState<string | null>(null);
116
+
117
+ // Fetch clientId from Engine on mount
118
+ // Engine manages the Thirdweb clientId centrally
119
+ useEffect(() => {
120
+ const fetchClientConfig = async () => {
121
+ try {
122
+ const engineUrl = config.engineUrl || DEFAULT_ENGINE_URL;
123
+ const response = await fetch(`${engineUrl}/v1/config/thirdweb`);
124
+
125
+ if (response.ok) {
126
+ const data = await response.json();
127
+ if (data.success && data.data?.clientId) {
128
+ setClientId(data.data.clientId);
129
+ } else {
130
+ // Fallback to environment variable if API fails
131
+ const envClientId = process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID;
132
+ if (envClientId) {
133
+ setClientId(envClientId);
134
+ } else {
135
+ setError('Failed to load wallet configuration');
136
+ }
137
+ }
138
+ } else {
139
+ // Fallback to environment variable
140
+ const envClientId = process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID;
141
+ if (envClientId) {
142
+ setClientId(envClientId);
143
+ } else {
144
+ setError('Wallet service unavailable');
145
+ }
146
+ }
147
+ } catch (err) {
148
+ // Fallback to environment variable on network error
149
+ const envClientId = process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID;
150
+ if (envClientId) {
151
+ setClientId(envClientId);
152
+ } else {
153
+ console.error('Failed to fetch thirdweb config:', err);
154
+ setError('Failed to initialize wallet');
155
+ }
156
+ } finally {
157
+ setIsLoading(false);
158
+ }
159
+ };
160
+
161
+ fetchClientConfig();
162
+ }, [config.engineUrl]);
163
+
164
+ // Create thirdweb client once we have clientId
165
+ const client = useMemo(() => {
166
+ if (!clientId) return null;
167
+ return createThirdwebClient({ clientId });
168
+ }, [clientId]);
169
+
170
+ // Create wallet configuration
171
+ const wallets = useMemo(() => createWalletConfig(config), [config]);
172
+
173
+ // Loading state
174
+ if (isLoading) {
175
+ return (
176
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100px' }}>
177
+ <span style={{ color: '#9ca3af' }}>Initializing wallet...</span>
178
+ </div>
179
+ );
180
+ }
181
+
182
+ // Error state
183
+ if (error || !client) {
184
+ return (
185
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100px' }}>
186
+ <span style={{ color: '#ef4444' }}>{error || 'Wallet initialization failed'}</span>
187
+ </div>
188
+ );
189
+ }
190
+
191
+ return (
192
+ <ThirdwebClientContext.Provider value={client}>
193
+ <BaseThirdwebProvider>
194
+ {children}
195
+ </BaseThirdwebProvider>
196
+ </ThirdwebClientContext.Provider>
197
+ );
198
+ }
199
+
200
+ // ===== Export wallets for external use =====
201
+
202
+ export { inAppWallet, smartWallet } from 'thirdweb/wallets';
203
+ export { base, ethereum, polygon, arbitrum, optimism } from 'thirdweb/chains';
@@ -0,0 +1,26 @@
1
+ export {
2
+ OneProvider,
3
+ useOne,
4
+ useOneAuth,
5
+ useOneWallet,
6
+ useOneOnramp,
7
+ useOneSwap,
8
+ useOneTrading,
9
+ useOneEngine,
10
+ OneContext,
11
+ } from './OneProvider';
12
+
13
+ // Thirdweb Integration
14
+ export {
15
+ OneThirdwebProvider,
16
+ useThirdwebClient,
17
+ inAppWallet,
18
+ smartWallet,
19
+ base,
20
+ ethereum,
21
+ polygon,
22
+ arbitrum,
23
+ optimism,
24
+ type OneThirdwebConfig,
25
+ type OneThirdwebProviderProps,
26
+ } from './ThirdwebProvider';
@@ -0,0 +1,378 @@
1
+ /**
2
+ * ONE SDK - React Native Entry Point
3
+ *
4
+ * This file exports SDK features optimized for React Native usage.
5
+ * It excludes web-specific features and includes mobile-specific utilities.
6
+ */
7
+
8
+ // ===== Configuration =====
9
+ export { initOneSDK, getConfig, CHAIN_CONFIGS, TOKEN_NAMES, COINGECKO_IDS } from './config';
10
+ export type { OneConfig } from './config';
11
+
12
+ // ===== Types =====
13
+ export * from './types';
14
+
15
+ // ===== ONE Engine Client =====
16
+ import { OneEngineClient as _OneEngineClient, createOneEngineClient } from './services/engine';
17
+ export { _OneEngineClient as OneEngineClient, createOneEngineClient };
18
+
19
+ export type {
20
+ EngineAuthResponse,
21
+ EngineWalletBalance,
22
+ EngineTransactionRequest,
23
+ EngineTransactionResponse,
24
+ OnrampSessionRequest,
25
+ OnrampSession,
26
+ OnrampQuote,
27
+ OnrampTransaction,
28
+ SwapQuoteRequest,
29
+ SwapQuote,
30
+ SwapExecuteRequest,
31
+ SwapResult,
32
+ } from './services/engine';
33
+
34
+ // ===== Price Service =====
35
+ export { PriceService, priceService } from './services/price';
36
+
37
+ // ===== Utilities =====
38
+ export {
39
+ // Address
40
+ shortenAddress,
41
+ isValidAddress,
42
+ checksumAddress,
43
+ // Numbers
44
+ formatNumber,
45
+ formatUSD,
46
+ formatPercent,
47
+ formatTokenAmount,
48
+ // Date/Time
49
+ formatDate,
50
+ formatDateTime,
51
+ formatRelativeTime,
52
+ // Validation
53
+ isValidEmail,
54
+ isValidPhone,
55
+ // Strings
56
+ capitalize,
57
+ truncate,
58
+ slugify,
59
+ // Async
60
+ sleep,
61
+ retry,
62
+ // Objects
63
+ omit,
64
+ pick,
65
+ // Errors
66
+ OneSDKError,
67
+ isOneSDKError,
68
+ } from './utils';
69
+
70
+ // ===== React Native Specific Utilities =====
71
+
72
+ /**
73
+ * Storage adapter interface for React Native
74
+ * Implement this with AsyncStorage or other storage solutions
75
+ */
76
+ export interface StorageAdapter {
77
+ getItem(key: string): Promise<string | null>;
78
+ setItem(key: string, value: string): Promise<void>;
79
+ removeItem(key: string): Promise<void>;
80
+ clear(): Promise<void>;
81
+ }
82
+
83
+ /**
84
+ * Create a cached engine client with persistent storage
85
+ */
86
+ export function createCachedEngineClient(
87
+ storage: StorageAdapter,
88
+ options?: {
89
+ baseUrl?: string;
90
+ clientId?: string;
91
+ secretKey?: string;
92
+ }
93
+ ) {
94
+ const client = createOneEngineClient(options);
95
+
96
+ // Wrapper with token persistence
97
+ return {
98
+ ...client,
99
+
100
+ /**
101
+ * Initialize with stored token
102
+ */
103
+ async initialize(): Promise<boolean> {
104
+ const token = await storage.getItem('one_access_token');
105
+ if (token) {
106
+ client.setAccessToken(token);
107
+ return true;
108
+ }
109
+ return false;
110
+ },
111
+
112
+ /**
113
+ * Login and persist token
114
+ */
115
+ async login(email: string, otp: string) {
116
+ const result = await client.verifyEmailOtp(email, otp);
117
+ if (result.success && result.data?.accessToken) {
118
+ await storage.setItem('one_access_token', result.data.accessToken);
119
+ if (result.data.refreshToken) {
120
+ await storage.setItem('one_refresh_token', result.data.refreshToken);
121
+ }
122
+ }
123
+ return result;
124
+ },
125
+
126
+ /**
127
+ * Logout and clear stored tokens
128
+ */
129
+ async logout() {
130
+ await client.signOut();
131
+ await storage.removeItem('one_access_token');
132
+ await storage.removeItem('one_refresh_token');
133
+ client.clearAccessToken();
134
+ },
135
+
136
+ /**
137
+ * Refresh token from storage
138
+ */
139
+ async refreshFromStorage() {
140
+ const refreshToken = await storage.getItem('one_refresh_token');
141
+ if (refreshToken) {
142
+ const result = await client.refreshToken(refreshToken);
143
+ if (result.success && result.data?.accessToken) {
144
+ await storage.setItem('one_access_token', result.data.accessToken);
145
+ if (result.data.refreshToken) {
146
+ await storage.setItem('one_refresh_token', result.data.refreshToken);
147
+ }
148
+ return true;
149
+ }
150
+ }
151
+ return false;
152
+ },
153
+
154
+ // Expose underlying client
155
+ getClient: () => client,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Deep linking handler for ONE SDK
161
+ */
162
+ export interface DeepLinkHandler {
163
+ /**
164
+ * Parse deep link URL
165
+ */
166
+ parse(url: string): {
167
+ type: 'onramp_callback' | 'wallet_connect' | 'payment' | 'unknown';
168
+ params: Record<string, string>;
169
+ };
170
+
171
+ /**
172
+ * Generate deep link URL
173
+ */
174
+ generate(type: string, params: Record<string, string>): string;
175
+ }
176
+
177
+ export function createDeepLinkHandler(scheme: string = 'onewallet'): DeepLinkHandler {
178
+ return {
179
+ parse(url: string) {
180
+ try {
181
+ const urlObj = new URL(url);
182
+ const path = urlObj.pathname.replace(/^\//, '');
183
+ const params: Record<string, string> = {};
184
+
185
+ urlObj.searchParams.forEach((value, key) => {
186
+ params[key] = value;
187
+ });
188
+
189
+ let type: 'onramp_callback' | 'wallet_connect' | 'payment' | 'unknown' = 'unknown';
190
+
191
+ if (path.includes('onramp') || path.includes('callback')) {
192
+ type = 'onramp_callback';
193
+ } else if (path.includes('wc') || path.includes('walletconnect')) {
194
+ type = 'wallet_connect';
195
+ } else if (path.includes('pay') || path.includes('payment')) {
196
+ type = 'payment';
197
+ }
198
+
199
+ return { type, params };
200
+ } catch {
201
+ return { type: 'unknown', params: {} };
202
+ }
203
+ },
204
+
205
+ generate(type: string, params: Record<string, string>): string {
206
+ const searchParams = new URLSearchParams(params);
207
+ return `${scheme}://${type}?${searchParams.toString()}`;
208
+ },
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Biometric authentication helper types
214
+ */
215
+ export interface BiometricConfig {
216
+ promptMessage: string;
217
+ cancelButtonText?: string;
218
+ fallbackLabel?: string;
219
+ }
220
+
221
+ export interface BiometricResult {
222
+ success: boolean;
223
+ error?: string;
224
+ }
225
+
226
+ /**
227
+ * QR Code scanning result types
228
+ */
229
+ export interface QRScanResult {
230
+ type: 'address' | 'wallet_connect' | 'payment_request' | 'unknown';
231
+ data: string;
232
+ parsed?: {
233
+ address?: string;
234
+ chainId?: number;
235
+ amount?: string;
236
+ token?: string;
237
+ message?: string;
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Parse QR code data for wallet operations
243
+ */
244
+ export function parseQRCode(data: string): QRScanResult {
245
+ // Ethereum address
246
+ if (/^0x[a-fA-F0-9]{40}$/.test(data)) {
247
+ return {
248
+ type: 'address',
249
+ data,
250
+ parsed: { address: data },
251
+ };
252
+ }
253
+
254
+ // EIP-681 payment request (ethereum:0x...?value=...&token=...)
255
+ if (data.startsWith('ethereum:')) {
256
+ const match = data.match(/^ethereum:(0x[a-fA-F0-9]{40})(?:@(\d+))?(?:\?(.*))?$/);
257
+ if (match) {
258
+ const [, address, chainId, queryString] = match;
259
+ const params = new URLSearchParams(queryString || '');
260
+ return {
261
+ type: 'payment_request',
262
+ data,
263
+ parsed: {
264
+ address,
265
+ chainId: chainId ? parseInt(chainId) : undefined,
266
+ amount: params.get('value') || undefined,
267
+ token: params.get('token') || undefined,
268
+ message: params.get('message') || undefined,
269
+ },
270
+ };
271
+ }
272
+ }
273
+
274
+ // WalletConnect URI
275
+ if (data.startsWith('wc:')) {
276
+ return {
277
+ type: 'wallet_connect',
278
+ data,
279
+ };
280
+ }
281
+
282
+ return { type: 'unknown', data };
283
+ }
284
+
285
+ /**
286
+ * Format crypto amount for display on mobile
287
+ */
288
+ export function formatCryptoAmount(
289
+ amount: number | string,
290
+ symbol: string,
291
+ options: {
292
+ maxDecimals?: number;
293
+ showSymbol?: boolean;
294
+ compact?: boolean;
295
+ } = {}
296
+ ): string {
297
+ const { maxDecimals = 6, showSymbol = true, compact = false } = options;
298
+ const num = typeof amount === 'string' ? parseFloat(amount) : amount;
299
+
300
+ if (isNaN(num)) return showSymbol ? `0 ${symbol}` : '0';
301
+
302
+ let formatted: string;
303
+
304
+ if (compact && num >= 1000000) {
305
+ formatted = (num / 1000000).toFixed(2) + 'M';
306
+ } else if (compact && num >= 1000) {
307
+ formatted = (num / 1000).toFixed(2) + 'K';
308
+ } else if (num < 0.000001 && num > 0) {
309
+ formatted = '<0.000001';
310
+ } else {
311
+ const decimals = num < 1 ? maxDecimals : Math.min(maxDecimals, 4);
312
+ formatted = num.toFixed(decimals).replace(/\.?0+$/, '');
313
+ }
314
+
315
+ return showSymbol ? `${formatted} ${symbol}` : formatted;
316
+ }
317
+
318
+ /**
319
+ * Generate share content for transactions
320
+ */
321
+ export function generateShareContent(params: {
322
+ type: 'receive' | 'payment_request' | 'transaction';
323
+ address?: string;
324
+ amount?: string;
325
+ token?: string;
326
+ txHash?: string;
327
+ chainId?: number;
328
+ }): { title: string; message: string; url?: string } {
329
+ const { type, address, amount, token, txHash, chainId } = params;
330
+
331
+ switch (type) {
332
+ case 'receive':
333
+ return {
334
+ title: 'My Wallet Address',
335
+ message: `Send ${token || 'crypto'} to my wallet:\n${address}`,
336
+ url: address ? `ethereum:${address}${chainId ? `@${chainId}` : ''}` : undefined,
337
+ };
338
+
339
+ case 'payment_request':
340
+ return {
341
+ title: 'Payment Request',
342
+ message: `Please send ${amount} ${token} to:\n${address}`,
343
+ url: `ethereum:${address}${chainId ? `@${chainId}` : ''}?value=${amount}${token ? `&token=${token}` : ''}`,
344
+ };
345
+
346
+ case 'transaction':
347
+ return {
348
+ title: 'Transaction Sent',
349
+ message: `Transaction confirmed!\nHash: ${txHash}`,
350
+ url: txHash ? getExplorerUrl(chainId || 1, txHash, 'tx') : undefined,
351
+ };
352
+
353
+ default:
354
+ return { title: '', message: '' };
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Get block explorer URL
360
+ */
361
+ export function getExplorerUrl(
362
+ chainId: number,
363
+ hash: string,
364
+ type: 'tx' | 'address' | 'token' = 'tx'
365
+ ): string {
366
+ const explorers: Record<number, string> = {
367
+ 1: 'https://etherscan.io',
368
+ 137: 'https://polygonscan.com',
369
+ 42161: 'https://arbiscan.io',
370
+ 10: 'https://optimistic.etherscan.io',
371
+ 8453: 'https://basescan.org',
372
+ 56: 'https://bscscan.com',
373
+ 43114: 'https://snowtrace.io',
374
+ };
375
+
376
+ const baseUrl = explorers[chainId] || explorers[1];
377
+ return `${baseUrl}/${type}/${hash}`;
378
+ }