@monygroupcorp/micro-web3 0.1.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.
@@ -0,0 +1,567 @@
1
+ import { Component } from '@monygroupcorp/microact';
2
+ import WalletModal from './WalletModal.js';
3
+
4
+ export class WalletSplash extends Component {
5
+ constructor(props) {
6
+ super(props);
7
+ this.onConnected = props.onConnected;
8
+ this.this.walletService = props.this.walletService;
9
+ this.this.eventBus = props.this.eventBus;
10
+
11
+ this.walletModal = null;
12
+ this.state = {
13
+ walletConnected: false,
14
+ checking: true,
15
+ walletAvailable: false,
16
+ loadingLightNode: false
17
+ };
18
+ }
19
+
20
+ async onMount() {
21
+ // Check if wallet is already connected
22
+ await this.checkWalletConnection();
23
+
24
+ // Set up event listeners
25
+ this.setupEventListeners();
26
+ }
27
+
28
+ /**
29
+ * Detect which wallet provider is being used
30
+ * @returns {string|null} Wallet type or null
31
+ */
32
+ detectWalletType() {
33
+ if (typeof window.ethereum === 'undefined') {
34
+ return null;
35
+ }
36
+
37
+ // Check for specific wallet identifiers
38
+ if (window.ethereum.isRabby) {
39
+ return 'rabby';
40
+ }
41
+ if (window.ethereum.isRainbow) {
42
+ return 'rainbow';
43
+ }
44
+ if (window.phantom && window.phantom.ethereum) {
45
+ return 'phantom';
46
+ }
47
+ if (window.ethereum.isMetaMask) {
48
+ return 'metamask';
49
+ }
50
+
51
+ // Default to metamask if window.ethereum exists but no specific identifier
52
+ return 'metamask';
53
+ }
54
+
55
+ async checkWalletConnection() {
56
+ try {
57
+ // Check if web3 wallet is available (window.ethereum exists)
58
+ const walletAvailable = typeof window.ethereum !== 'undefined';
59
+
60
+ // Initialize wallet service if needed
61
+ if (!this.walletService.isInitialized) {
62
+ await this.walletService.initialize();
63
+ }
64
+
65
+ // Check if wallet service thinks it's connected
66
+ let isConnected = this.walletService.isConnected();
67
+
68
+ // If not connected, try to auto-reconnect to the last used wallet
69
+ // This allows auto-reconnect on refresh without annoying prompts
70
+ if (!isConnected && typeof window.ethereum !== 'undefined') {
71
+ try {
72
+ // Get the last used wallet from localStorage
73
+ const lastWallet = localStorage.getItem('ms2fun_lastWallet');
74
+
75
+ if (lastWallet) {
76
+ // Check if that wallet has accounts (without prompting)
77
+ let hasAccounts = false;
78
+ try {
79
+ // Get the provider for the last wallet
80
+ const providerMap = {
81
+ rabby: () => window.ethereum?.isRabby ? window.ethereum : null,
82
+ rainbow: () => window.ethereum?.isRainbow ? window.ethereum : null,
83
+ phantom: () => window.phantom?.ethereum || null,
84
+ metamask: () => window.ethereum || null
85
+ };
86
+
87
+ const getProvider = providerMap[lastWallet];
88
+ if (getProvider) {
89
+ const provider = getProvider();
90
+ if (provider) {
91
+ const accounts = await provider.request({ method: 'eth_accounts' });
92
+ hasAccounts = accounts && accounts.length > 0;
93
+ }
94
+ }
95
+ } catch (error) {
96
+ // Can't check - that's fine
97
+ console.log('Could not check accounts for last wallet:', error);
98
+ }
99
+
100
+ // Only try to reconnect if the last wallet has accounts
101
+ if (hasAccounts) {
102
+ try {
103
+ // Select the last used wallet (doesn't prompt)
104
+ await this.walletService.selectWallet(lastWallet);
105
+ // Try to connect (this will use existing connection if available)
106
+ // If it needs approval, it will fail gracefully and user can click button
107
+ await this.walletService.connect();
108
+ isConnected = this.walletService.isConnected();
109
+ if (isConnected) {
110
+ console.log('Auto-reconnected to', lastWallet);
111
+ }
112
+ } catch (connectError) {
113
+ // Connection failed (user needs to approve) - that's fine
114
+ // Don't log as error, just continue to show wallet selection
115
+ console.log('Auto-reconnect not possible, user will need to select wallet');
116
+ }
117
+ }
118
+ }
119
+ } catch (accountsError) {
120
+ // Can't check accounts - that's fine, show wallet selection
121
+ console.log('Could not check existing accounts:', accountsError);
122
+ }
123
+ }
124
+
125
+ this.setState({
126
+ walletConnected: isConnected,
127
+ checking: false,
128
+ walletAvailable: walletAvailable
129
+ });
130
+
131
+ // If already connected, call onConnected callback
132
+ if (isConnected && this.onConnected) {
133
+ this.onConnected();
134
+ } else {
135
+ // If not connected, ensure wallet modal is set up
136
+ // Use setTimeout to ensure state update has processed
137
+ this.setTimeout(() => {
138
+ this.setupWalletModal();
139
+ }, 100);
140
+ }
141
+ } catch (error) {
142
+ console.error('Error checking wallet connection:', error);
143
+ // Check wallet availability even on error
144
+ const walletAvailable = typeof window.ethereum !== 'undefined';
145
+ this.setState({
146
+ walletConnected: false,
147
+ checking: false,
148
+ walletAvailable: walletAvailable
149
+ });
150
+ // Ensure wallet modal is set up even on error
151
+ this.setTimeout(() => {
152
+ this.setupWalletModal();
153
+ }, 100);
154
+ }
155
+ }
156
+
157
+ setupEventListeners() {
158
+ // Listen for wallet connection
159
+ const unsubscribeConnected = this.eventBus.on('wallet:connected', () => {
160
+ this.setState({ walletConnected: true });
161
+ if (this.onConnected) {
162
+ this.onConnected();
163
+ }
164
+ });
165
+
166
+ // Listen for wallet disconnection
167
+ const unsubscribeDisconnected = this.eventBus.on('wallet:disconnected', () => {
168
+ this.setState({ walletConnected: false });
169
+ });
170
+
171
+ // Register cleanup
172
+ this.registerCleanup(() => {
173
+ unsubscribeConnected();
174
+ unsubscribeDisconnected();
175
+ });
176
+ }
177
+
178
+ render() {
179
+ if (this.state.checking) {
180
+ return `
181
+ <div class="wallet-splash">
182
+ <div class="splash-content marble-bg">
183
+ <div class="splash-spinner"></div>
184
+ <h2>Checking wallet connection...</h2>
185
+ </div>
186
+ </div>
187
+ `;
188
+ }
189
+
190
+ if (this.state.walletConnected) {
191
+ // Wallet is connected, hide splash (content will be shown)
192
+ return '<div class="wallet-splash-connected" style="display: none;"></div>';
193
+ }
194
+
195
+ // Wallet not connected, show splash screen
196
+ const walletAvailable = this.state.walletAvailable;
197
+ const loadingLightNode = this.state.loadingLightNode;
198
+
199
+ return `
200
+ <div class="wallet-splash">
201
+ <div class="splash-content marble-bg">
202
+ <div class="splash-header">
203
+ <h1>Connect Your Wallet</h1>
204
+ <p class="splash-subtitle">Connect your wallet to access the MS2.FUN launchpad</p>
205
+ </div>
206
+
207
+ <div class="splash-description">
208
+ <p>This application requires a connected wallet to access on-chain data and interact with projects.</p>
209
+ ${walletAvailable
210
+ ? '<p>You can connect your wallet or continue using your wallet\'s RPC for read-only access.</p>'
211
+ : '<p>No wallet detected. You can continue with read-only mode using a light node.</p>'}
212
+ </div>
213
+
214
+ <div class="wallet-connector-container" data-ref="wallet-connector">
215
+ <div class="contract-status">
216
+ <div id="contractStatus" class="status-message">${loadingLightNode ? 'DOWNLOADING LIGHT NODE...' : 'INITIALIZING SYSTEM...'}</div>
217
+
218
+ <div id="selectedWalletDisplay" class="selected-wallet-display" style="display: none;">
219
+ <img id="selectedWalletIcon" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'/%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'/%3E%3C/svg%3E" alt="Selected Wallet">
220
+ <span class="wallet-name" id="selectedWalletName"></span>
221
+ </div>
222
+ <div id="continuePrompt" class="continue-prompt" style="display: none;">
223
+ CONTINUE IN YOUR WALLET
224
+ </div>
225
+
226
+ <button id="selectWallet" class="connect-button" ${!walletAvailable ? 'disabled' : ''} style="${!walletAvailable ? 'opacity: 0.5; cursor: not-allowed;' : ''}">
227
+ <span class="button-text">SELECT WALLET</span>
228
+ </button>
229
+
230
+ <button id="continueButton" class="connect-button" style="margin-top: 1rem; ${loadingLightNode ? 'opacity: 0.7; cursor: wait;' : ''}">
231
+ <span class="button-text">${loadingLightNode ? 'LOADING...' : 'CONTINUE'}</span>
232
+ </button>
233
+
234
+ ${!walletAvailable ? `
235
+ <p class="light-node-explainer" style="margin-top: 1rem; font-size: 0.875rem; color: rgba(255, 255, 255, 0.7); text-align: center; max-width: 400px; margin-left: auto; margin-right: auto;">
236
+ This will download and run a light node to enable read-only blockchain access without a wallet.
237
+ </p>
238
+ ` : ''}
239
+ </div>
240
+
241
+ <!-- Wallet Selection Modal (same as CultExecsPage) -->
242
+ <div id="walletModal" class="wallet-modal">
243
+ <div class="wallet-modal-content">
244
+ <div class="wallet-modal-header">
245
+ <h3>Select Your Wallet</h3>
246
+ <button class="wallet-modal-close" data-ref="modal-close">×</button>
247
+ </div>
248
+ <div class="wallet-options">
249
+ <button class="wallet-option" data-wallet="rabby">
250
+ <picture>
251
+ <source srcset="public/wallets/rabby.avif" type="image/avif">
252
+ <source srcset="public/wallets/rabby.webp" type="image/webp">
253
+ <img src="public/wallets/rabby.png" alt="Rabby">
254
+ </picture>
255
+ <span>Rabby</span>
256
+ </button>
257
+ <button class="wallet-option" data-wallet="rainbow">
258
+ <picture>
259
+ <source srcset="public/wallets/rainbow.avif" type="image/avif">
260
+ <source srcset="public/wallets/rainbow.webp" type="image/webp">
261
+ <img src="public/wallets/rainbow.png" alt="Rainbow">
262
+ </picture>
263
+ <span>Rainbow</span>
264
+ </button>
265
+ <button class="wallet-option" data-wallet="phantom">
266
+ <picture>
267
+ <source srcset="public/wallets/phantom.avif" type="image/avif">
268
+ <source srcset="public/wallets/phantom.webp" type="image/webp">
269
+ <img src="public/wallets/phantom.png" alt="Phantom">
270
+ </picture>
271
+ <span>Phantom</span>
272
+ </button>
273
+ <button class="wallet-option" data-wallet="metamask">
274
+ <picture>
275
+ <source srcset="/public/wallets/MetaMask.avif" type="image/avif">
276
+ <source srcset="/public/wallets/MetaMask.webp" type="image/webp">
277
+ <img src="/public/wallets/MetaMask.webp" alt="MetaMask" onerror="this.src='/public/wallets/MetaMask.webp'">
278
+ </picture>
279
+ <span>MetaMask</span>
280
+ </button>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ `;
288
+ }
289
+
290
+ mount(element) {
291
+ super.mount(element);
292
+
293
+ // Set up wallet modal after mount
294
+ this.setTimeout(() => {
295
+ this.setupWalletModal();
296
+ }, 0);
297
+ }
298
+
299
+ setupWalletModal() {
300
+ // Only set up if wallet is not connected
301
+ if (this.state.walletConnected) {
302
+ return;
303
+ }
304
+
305
+ // Prevent infinite retry loops
306
+ if (!this._setupRetryCount) {
307
+ this._setupRetryCount = 0;
308
+ }
309
+ if (this._setupRetryCount > 10) {
310
+ console.error('Wallet modal setup failed after 10 retries - giving up');
311
+ return;
312
+ }
313
+ this._setupRetryCount++;
314
+
315
+ // Wait for DOM to be ready
316
+ this.setTimeout(() => {
317
+ const modal = document.getElementById('walletModal');
318
+ const selectButton = document.getElementById('selectWallet');
319
+
320
+ if (!modal || !selectButton) {
321
+ console.error('Wallet modal or select button not found, retry', this._setupRetryCount);
322
+ // Retry after a short delay if elements aren't ready yet
323
+ if (this.mounted && this._setupRetryCount <= 10) {
324
+ this.setTimeout(() => {
325
+ this.setupWalletModal();
326
+ }, 200);
327
+ }
328
+ return;
329
+ }
330
+
331
+ // Reset retry count on success
332
+ this._setupRetryCount = 0;
333
+
334
+ // Create WalletModal instance
335
+ if (!this.walletModal) {
336
+ // Get provider map and icons from this.walletService
337
+ const providerMap = {
338
+ rabby: () => window.ethereum?.isRabby ? window.ethereum : null,
339
+ rainbow: () => window.ethereum?.isRainbow ? window.ethereum : null,
340
+ phantom: () => window.phantom?.ethereum || null,
341
+ metamask: () => window.ethereum || null
342
+ };
343
+
344
+ const walletIcons = {
345
+ rabby: '/public/wallets/rabby.webp',
346
+ rainbow: '/public/wallets/rainbow.webp',
347
+ phantom: '/public/wallets/phantom.webp',
348
+ metamask: '/public/wallets/MetaMask.webp'
349
+ };
350
+
351
+ // Create WalletModal with callback
352
+ this.walletModal = new WalletModal(
353
+ providerMap,
354
+ walletIcons,
355
+ async (walletType) => {
356
+ await this.handleWalletSelection(walletType);
357
+ }
358
+ );
359
+ }
360
+
361
+ // Set up select button click handler
362
+ // Clone button to remove any existing handlers
363
+ const newButton = selectButton.cloneNode(true);
364
+ selectButton.parentNode.replaceChild(newButton, selectButton);
365
+
366
+ newButton.addEventListener('click', (e) => {
367
+ e.preventDefault();
368
+ e.stopPropagation();
369
+
370
+ // Don't do anything if wallet is not available
371
+ if (!this.state.walletAvailable) {
372
+ return;
373
+ }
374
+
375
+ console.log('SELECT WALLET button clicked');
376
+
377
+ if (this.walletModal) {
378
+ console.log('Calling walletModal.show()');
379
+ this.walletModal.show();
380
+
381
+ // Double-check modal is visible
382
+ const modalEl = document.getElementById('walletModal');
383
+ if (modalEl) {
384
+ console.log('Modal element found, checking visibility');
385
+ console.log('Modal classes:', modalEl.className);
386
+ console.log('Modal display:', window.getComputedStyle(modalEl).display);
387
+ modalEl.style.display = 'flex';
388
+ modalEl.classList.add('active');
389
+ } else {
390
+ console.error('Modal element not found after show()');
391
+ }
392
+ } else {
393
+ console.error('WalletModal not initialized');
394
+ }
395
+ });
396
+
397
+ // Set up continue button click handler
398
+ const continueButton = document.getElementById('continueButton');
399
+ if (continueButton) {
400
+ const newContinueButton = continueButton.cloneNode(true);
401
+ continueButton.parentNode.replaceChild(newContinueButton, continueButton);
402
+
403
+ newContinueButton.addEventListener('click', async (e) => {
404
+ e.preventDefault();
405
+ e.stopPropagation();
406
+
407
+ // If wallet is available (detected), just continue
408
+ // The site can use window.ethereum for RPC reads even if not connected
409
+ if (this.state.walletAvailable) {
410
+ if (this.onConnected) {
411
+ this.onConnected();
412
+ }
413
+ return;
414
+ }
415
+
416
+ // If no wallet available, load light node
417
+ if (!this.state.walletAvailable) {
418
+ await this.handleContinueWithoutWallet();
419
+ }
420
+ });
421
+ }
422
+
423
+ // Set up close button handler
424
+ const closeButton = modal.querySelector('.wallet-modal-close');
425
+ if (closeButton) {
426
+ // Remove any existing listeners by cloning
427
+ const newCloseButton = closeButton.cloneNode(true);
428
+ closeButton.parentNode.replaceChild(newCloseButton, closeButton);
429
+
430
+ newCloseButton.addEventListener('click', (e) => {
431
+ e.preventDefault();
432
+ e.stopPropagation();
433
+ if (this.walletModal) {
434
+ this.walletModal.hide();
435
+ } else {
436
+ // Fallback if walletModal not initialized
437
+ const modalEl = document.getElementById('walletModal');
438
+ if (modalEl) {
439
+ modalEl.classList.remove('active');
440
+ modalEl.style.display = 'none';
441
+ }
442
+ }
443
+ });
444
+ }
445
+
446
+ // Set up click outside to close
447
+ // WalletModal already has this, but we'll ensure it works
448
+ // Don't clone modal as it breaks WalletModal's reference
449
+ const handleModalClick = (e) => {
450
+ if (e.target === modal) {
451
+ if (this.walletModal) {
452
+ this.walletModal.hide();
453
+ } else {
454
+ modal.classList.remove('active');
455
+ modal.style.display = 'none';
456
+ }
457
+ }
458
+ };
459
+
460
+ // Remove existing listener if any, then add new one
461
+ modal.removeEventListener('click', handleModalClick);
462
+ modal.addEventListener('click', handleModalClick);
463
+ }, 100);
464
+ }
465
+
466
+ async handleWalletSelection(walletType) {
467
+ try {
468
+ // Select the wallet
469
+ await this.walletService.selectWallet(walletType);
470
+
471
+ // Store the selected wallet in localStorage for future auto-reconnect
472
+ localStorage.setItem('ms2fun_lastWallet', walletType);
473
+
474
+ // Connect to the wallet
475
+ await this.walletService.connect();
476
+
477
+ // Wallet is now connected
478
+ this.setState({ walletConnected: true });
479
+
480
+ if (this.onConnected) {
481
+ this.onConnected();
482
+ }
483
+ } catch (error) {
484
+ console.error('Error connecting wallet:', error);
485
+ // Show error - could use MessagePopup here
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Handle continue button click when no wallet is available
491
+ * Lazy-loads the light node and initializes read-only mode
492
+ */
493
+ async handleContinueWithoutWallet() {
494
+ if (this.state.loadingLightNode) {
495
+ return; // Already loading
496
+ }
497
+
498
+ this.setState({ loadingLightNode: true });
499
+
500
+ try {
501
+ console.log('[WalletSplash] Loading light node for read-only mode...');
502
+
503
+ // Dynamically import and initialize read-only mode
504
+ const { initializeReadOnlyMode } = await import('../../index.js');
505
+ const success = await initializeReadOnlyMode();
506
+
507
+ if (success) {
508
+ console.log('[WalletSplash] Light node loaded successfully');
509
+ // Continue to app (read-only mode)
510
+ if (this.onConnected) {
511
+ this.onConnected();
512
+ }
513
+ } else {
514
+ console.error('[WalletSplash] Failed to load light node');
515
+ // Show error message
516
+ const statusEl = document.getElementById('contractStatus');
517
+ if (statusEl) {
518
+ statusEl.textContent = 'FAILED TO LOAD LIGHT NODE. PLEASE TRY AGAIN.';
519
+ }
520
+ this.setState({ loadingLightNode: false });
521
+ }
522
+ } catch (error) {
523
+ console.error('[WalletSplash] Error loading light node:', error);
524
+ const statusEl = document.getElementById('contractStatus');
525
+ if (statusEl) {
526
+ statusEl.textContent = 'ERROR: ' + error.message;
527
+ }
528
+ this.setState({ loadingLightNode: false });
529
+ }
530
+ }
531
+
532
+ onStateUpdate(oldState, newState) {
533
+ // When wallet connects, hide splash
534
+ if (!oldState.walletConnected && newState.walletConnected) {
535
+ // Hide the splash screen
536
+ if (this.element) {
537
+ this.element.style.display = 'none';
538
+ }
539
+
540
+ // Hide modal if open
541
+ if (this.walletModal) {
542
+ this.walletModal.hide();
543
+ }
544
+ }
545
+
546
+ // When wallet disconnects, show splash again
547
+ if (oldState.walletConnected && !newState.walletConnected) {
548
+ // Show the splash screen
549
+ if (this.element) {
550
+ this.element.style.display = 'flex';
551
+ }
552
+
553
+ this.setTimeout(() => {
554
+ this.setupWalletModal();
555
+ }, 0);
556
+ }
557
+ }
558
+
559
+ onUnmount() {
560
+ // Clean up wallet modal
561
+ if (this.walletModal) {
562
+ this.walletModal.hide();
563
+ this.walletModal = null;
564
+ }
565
+ }
566
+ }
567
+
package/src/index.js ADDED
@@ -0,0 +1,43 @@
1
+ // Services
2
+ import BlockchainService from './services/BlockchainService.js';
3
+ import WalletService from './services/WalletService.js';
4
+ import ContractCache from './services/ContractCache.js';
5
+ import PriceService from './services/PriceService.js';
6
+ import * as IpfsService from './services/IpfsService.js';
7
+
8
+ // UI Components
9
+ import { WalletSplash } from './components/Wallet/WalletSplash.js';
10
+ import { WalletModal } from './components/Wallet/WalletModal.js';
11
+ import { IpfsImage } from './components/Ipfs/IpfsImage.js';
12
+ import { SwapInterface } from './components/Swap/SwapInterface.js';
13
+ import { SwapInputs } from './components/Swap/SwapInputs.js';
14
+ import { SwapButton } from './components/Swap/SwapButton.js';
15
+ import { TransactionOptions } from './components/Swap/TransactionOptions.js';
16
+ import { ApprovalModal } from './components/Modal/ApprovalModal.js';
17
+ import { BondingCurve } from './components/BondingCurve/BondingCurve.js';
18
+ import { PriceDisplay } from './components/Display/PriceDisplay.js';
19
+ import { BalanceDisplay } from './components/Display/BalanceDisplay.js';
20
+ import { MessagePopup } from './components/Util/MessagePopup.js';
21
+
22
+ export {
23
+ // Services
24
+ BlockchainService,
25
+ WalletService,
26
+ ContractCache,
27
+ PriceService,
28
+ IpfsService,
29
+
30
+ // UI Components
31
+ WalletSplash,
32
+ WalletModal,
33
+ IpfsImage,
34
+ SwapInterface,
35
+ SwapInputs,
36
+ SwapButton,
37
+ TransactionOptions,
38
+ ApprovalModal,
39
+ BondingCurve,
40
+ PriceDisplay,
41
+ BalanceDisplay,
42
+ MessagePopup
43
+ };