@monygroupcorp/micro-web3 1.2.3 → 1.3.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.
@@ -1,213 +1,221 @@
1
+ import { Component, h, render } from '@monygroupcorp/microact';
1
2
  import { FloatingWalletButton } from '../FloatingWalletButton/FloatingWalletButton.js';
2
3
  import WalletModal from '../Wallet/WalletModal.js';
3
4
 
4
5
  class WalletButtonModal extends WalletModal {
5
- events() {
6
- const baseEvents = typeof super.events === 'function' ? super.events() : {};
7
- return {
8
- ...baseEvents,
9
- 'click .wallet-option': async (e) => {
10
- const button = e?.target?.closest?.('.wallet-option');
11
- const walletType = button?.dataset?.wallet;
12
- if (!walletType) {
13
- return;
14
- }
15
- if (this.onWalletSelected) {
16
- await this.onWalletSelected(walletType);
6
+ handleWalletOptionClick(walletType) {
7
+ if (this.props.onWalletSelected) {
8
+ this.props.onWalletSelected(walletType);
17
9
  }
18
10
  this.hide();
19
- },
20
- };
21
- }
22
- }
11
+ }
23
12
 
24
- export class WalletButton extends FloatingWalletButton {
25
- constructor(props) {
26
- super(props);
27
- this.modalRoot = null;
28
- }
29
-
30
- async showWalletModal() {
31
- const providerMap =
32
- this.walletService?.providerMap ||
33
- {
34
- rabby: () => (window.ethereum?.isRabby ? window.ethereum : null),
35
- rainbow: () => (window.ethereum?.isRainbow ? window.ethereum : null),
36
- phantom: () => window.phantom?.ethereum || null,
37
- metamask: () => window.ethereum || null,
38
- };
39
-
40
- const walletIcons =
41
- this.walletService?.walletIcons ||
42
- {
43
- rabby: '/public/wallets/rabby.webp',
44
- rainbow: '/public/wallets/rainbow.webp',
45
- phantom: '/public/wallets/phantom.webp',
46
- metamask: '/public/wallets/MetaMask.webp',
47
- };
48
-
49
- if (!this.walletModal) {
50
- this.walletModal = new WalletButtonModal({
51
- providerMap,
52
- walletIcons,
53
- onWalletSelected: async (walletType) => {
54
- await this.handleWalletSelection(walletType);
55
- },
56
- });
13
+ render() {
14
+ if (!this.state.isVisible) {
15
+ return h('div', { className: 'wallet-modal-container', style: { display: 'none' } });
16
+ }
57
17
 
58
- if (!this.modalRoot) {
59
- this.modalRoot = document.createElement('div');
60
- document.body.appendChild(this.modalRoot);
61
- }
18
+ const walletOptions = Object.keys(this.props.providerMap);
19
+
20
+ return h('div', { className: 'wallet-modal-container' },
21
+ h('div', {
22
+ className: 'wallet-modal-overlay',
23
+ onClick: (e) => {
24
+ if (e.target === e.currentTarget) this.hide();
25
+ }
26
+ },
27
+ h('div', { className: 'wallet-modal' },
28
+ h('div', { className: 'wallet-modal-header' },
29
+ h('h3', null, 'Select Your Wallet'),
30
+ h('button', {
31
+ className: 'wallet-modal-close',
32
+ onClick: this.bind(this.hide)
33
+ }, '\u00D7')
34
+ ),
35
+ h('div', { className: 'wallet-options' },
36
+ walletOptions.map(walletType =>
37
+ h('button', {
38
+ key: walletType,
39
+ className: 'wallet-option',
40
+ onClick: () => this.handleWalletOptionClick(walletType)
41
+ },
42
+ h('img', {
43
+ src: this.props.walletIcons[walletType],
44
+ alt: walletType
45
+ }),
46
+ h('span', null, walletType.charAt(0).toUpperCase() + walletType.slice(1))
47
+ )
48
+ )
49
+ )
50
+ )
51
+ )
52
+ );
53
+ }
54
+ }
62
55
 
63
- this.walletModal.mount(this.modalRoot);
56
+ export class WalletButton extends FloatingWalletButton {
57
+ constructor(props) {
58
+ super(props);
59
+ this.modalRoot = null;
64
60
  }
65
61
 
66
- this.walletModal.show();
67
- }
62
+ showWalletModal() {
63
+ const providerMap =
64
+ this.walletService?.providerMap ||
65
+ {
66
+ rabby: () => (window.ethereum?.isRabby ? window.ethereum : null),
67
+ rainbow: () => (window.ethereum?.isRainbow ? window.ethereum : null),
68
+ phantom: () => window.phantom?.ethereum || null,
69
+ metamask: () => window.ethereum || null,
70
+ };
71
+
72
+ const walletIcons =
73
+ this.walletService?.walletIcons ||
74
+ {
75
+ rabby: '/public/wallets/rabby.webp',
76
+ rainbow: '/public/wallets/rainbow.webp',
77
+ phantom: '/public/wallets/phantom.webp',
78
+ metamask: '/public/wallets/MetaMask.webp',
79
+ };
80
+
81
+ if (!this.walletModal) {
82
+ if (!this.modalRoot) {
83
+ this.modalRoot = document.createElement('div');
84
+ document.body.appendChild(this.modalRoot);
85
+ }
86
+
87
+ render(h(WalletButtonModal, {
88
+ providerMap,
89
+ walletIcons,
90
+ onWalletSelected: async (walletType) => {
91
+ await this.handleWalletSelection(walletType);
92
+ },
93
+ ref: instance => this.walletModal = instance
94
+ }), this.modalRoot);
95
+ }
68
96
 
69
- onUnmount() {
70
- if (this.modalRoot?.parentNode) {
71
- this.modalRoot.parentNode.removeChild(this.modalRoot);
72
- this.modalRoot = null;
73
- }
74
- super.onUnmount();
75
- }
76
-
77
- render() {
78
- if (this.state.loading) {
79
- return `
80
- <div class="floating-wallet-button loading">
81
- <div class="wallet-spinner"></div>
82
- </div>
83
- `;
97
+ this.walletModal?.show();
84
98
  }
85
99
 
86
- const { walletConnected, address, balance, menuOpen } = this.state;
87
-
88
- if (!walletConnected) {
89
- return `
90
- <div class="floating-wallet-button disconnected" data-ref="wallet-button">
91
- <button class="wallet-btn" data-ref="connect-btn">
92
- <span class="wallet-mark"></span>
93
- <span class="wallet-text">Connect Wallet</span>
94
- </button>
95
- </div>
96
- `;
100
+ willUnmount() {
101
+ if (this.modalRoot?.parentNode) {
102
+ this.modalRoot.parentNode.removeChild(this.modalRoot);
103
+ this.modalRoot = null;
104
+ }
105
+ super.willUnmount();
97
106
  }
98
107
 
99
- const truncatedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
100
- const classNames = ['floating-wallet-button', 'connected'];
101
- if (menuOpen) {
102
- classNames.push('menu-open');
103
- }
108
+ render() {
109
+ if (this.state.loading) {
110
+ return h('div', { className: 'floating-wallet-button loading' },
111
+ h('div', { className: 'wallet-spinner' })
112
+ );
113
+ }
104
114
 
105
- const markup = `
106
- <div class="${classNames.join(' ')}" data-ref="wallet-button">
107
- <button class="wallet-btn" data-ref="wallet-btn" title="${this.escapeHtml(address)}\nBalance: ${balance} ETH">
108
- <span class="wallet-mark wallet-mark--connected"></span>
109
- <span class="wallet-address">${this.escapeHtml(truncatedAddress)}</span>
110
- <span class="wallet-disconnect" data-ref="inline-disconnect" title="Disconnect">⏏︎</span>
111
- </button>
112
- ${this.renderWalletPanel(address, balance, menuOpen)}
113
- </div>
114
- `;
115
-
116
- return this.minifyHtml(markup);
117
- }
118
-
119
- events() {
120
- const baseEvents = super.events?.() || {};
121
- return {
122
- ...baseEvents,
123
- 'click [data-ref="inline-disconnect"]': (e) => this.handleInlineDisconnect(e),
124
- 'click [data-ref="wallet-btn"]': (e) => this.handleWalletToggle(e),
125
- 'click [data-ref="connect-btn"]': (e) => this.handleWalletToggle(e),
126
- };
127
- }
128
-
129
- renderWalletPanel(address, balance, menuOpen) {
130
- const truncatedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
131
-
132
- const markup = `
133
- <div class="wallet-info-panel" data-ref="wallet-panel" aria-hidden="${menuOpen ? 'false' : 'true'}">
134
- <div class="wallet-info-row">
135
- <span class="wallet-info-label">Address</span>
136
- <span class="wallet-info-value">${this.escapeHtml(truncatedAddress)}</span>
137
- </div>
138
- <div class="wallet-info-row">
139
- <span class="wallet-info-label">ETH Balance</span>
140
- <span class="wallet-info-value">${balance} ETH</span>
141
- </div>
142
- </div>
143
- `;
144
-
145
- return this.minifyHtml(markup);
146
- }
147
-
148
- onStateUpdate(oldState, newState) {
149
- if (typeof super.onStateUpdate === 'function') {
150
- super.onStateUpdate(oldState, newState);
115
+ const { walletConnected, address, balance, menuOpen } = this.state;
116
+
117
+ if (!walletConnected) {
118
+ return h('div', { className: 'floating-wallet-button disconnected', ref: el => this.element = el },
119
+ h('button', {
120
+ className: 'wallet-btn',
121
+ onClick: this.bind(this.handleWalletToggle)
122
+ },
123
+ h('span', { className: 'wallet-mark' }),
124
+ h('span', { className: 'wallet-text' }, 'Connect Wallet')
125
+ )
126
+ );
127
+ }
128
+
129
+ const truncatedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
130
+ const classNames = ['floating-wallet-button', 'connected'];
131
+ if (menuOpen) {
132
+ classNames.push('menu-open');
133
+ }
134
+
135
+ return h('div', { className: classNames.join(' '), ref: el => this.element = el },
136
+ h('button', {
137
+ className: 'wallet-btn',
138
+ title: `${this.escapeHtml(address)}\nBalance: ${balance} ETH`,
139
+ onClick: this.bind(this.handleWalletToggle)
140
+ },
141
+ h('span', { className: 'wallet-mark wallet-mark--connected' }),
142
+ h('span', { className: 'wallet-address' }, this.escapeHtml(truncatedAddress)),
143
+ h('span', {
144
+ className: 'wallet-disconnect',
145
+ title: 'Disconnect',
146
+ onClick: this.bind(this.handleInlineDisconnect)
147
+ }, '\u23CF\uFE0E')
148
+ ),
149
+ this.renderWalletPanel(address, balance, menuOpen)
150
+ );
151
151
  }
152
- if (!oldState || oldState.menuOpen !== newState.menuOpen) {
153
- if (newState.menuOpen) {
154
- this.attachOutsideClickListener();
155
- } else {
156
- this.detachOutsideClickListener();
157
- }
152
+
153
+ renderWalletPanel(address, balance, menuOpen) {
154
+ const truncatedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
155
+
156
+ return h('div', {
157
+ className: 'wallet-info-panel',
158
+ 'aria-hidden': menuOpen ? 'false' : 'true'
159
+ },
160
+ h('div', { className: 'wallet-info-row' },
161
+ h('span', { className: 'wallet-info-label' }, 'Address'),
162
+ h('span', { className: 'wallet-info-value' }, this.escapeHtml(truncatedAddress))
163
+ ),
164
+ h('div', { className: 'wallet-info-row' },
165
+ h('span', { className: 'wallet-info-label' }, 'ETH Balance'),
166
+ h('span', { className: 'wallet-info-value' }, `${balance} ETH`)
167
+ )
168
+ );
158
169
  }
159
- }
160
-
161
- attachOutsideClickListener() {
162
- if (this.outsideClickHandler || this.outsideClickTimeout) return;
163
- this.outsideClickHandler = (event) => {
164
- if (!this.element) return;
165
- const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
166
- const clickedInside =
167
- (path.length && path.includes(this.element)) ||
168
- (event.target && this.element.contains(event.target));
169
- if (!clickedInside) {
170
- this.setState({ menuOpen: false });
171
- }
172
- };
173
- this.outsideClickTimeout = window.setTimeout(() => {
174
- document.addEventListener('click', this.outsideClickHandler);
175
- this.outsideClickTimeout = null;
176
- }, 0);
177
- }
178
-
179
- detachOutsideClickListener() {
180
- if (this.outsideClickTimeout) {
181
- window.clearTimeout(this.outsideClickTimeout);
182
- this.outsideClickTimeout = null;
170
+
171
+ didUpdate() {
172
+ if (this.state.menuOpen) {
173
+ this.attachOutsideClickListener();
174
+ } else {
175
+ this.detachOutsideClickListener();
176
+ }
183
177
  }
184
- if (this.outsideClickHandler) {
185
- document.removeEventListener('click', this.outsideClickHandler);
186
- this.outsideClickHandler = null;
178
+
179
+ attachOutsideClickListener() {
180
+ if (this.outsideClickHandler || this.outsideClickTimeout) return;
181
+ this.outsideClickHandler = (event) => {
182
+ if (!this.element) return;
183
+ const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
184
+ const clickedInside =
185
+ (path.length && path.includes(this.element)) ||
186
+ (event.target && this.element.contains(event.target));
187
+ if (!clickedInside) {
188
+ this.setState({ menuOpen: false });
189
+ }
190
+ };
191
+ this.outsideClickTimeout = window.setTimeout(() => {
192
+ document.addEventListener('click', this.outsideClickHandler);
193
+ this.outsideClickTimeout = null;
194
+ }, 0);
187
195
  }
188
- }
189
196
 
190
- setupDOMEventListeners() {
191
- // Delegated events handle wiring in this component.
192
- }
197
+ detachOutsideClickListener() {
198
+ if (this.outsideClickTimeout) {
199
+ window.clearTimeout(this.outsideClickTimeout);
200
+ this.outsideClickTimeout = null;
201
+ }
202
+ if (this.outsideClickHandler) {
203
+ document.removeEventListener('click', this.outsideClickHandler);
204
+ this.outsideClickHandler = null;
205
+ }
206
+ }
193
207
 
194
- handleWalletToggle(e) {
195
- this.handleButtonClick(e);
196
- }
208
+ handleWalletToggle(e) {
209
+ this.handleButtonClick(e);
210
+ }
197
211
 
198
- handleInlineDisconnect(e) {
199
- if (e) {
200
- e.preventDefault();
201
- e.stopPropagation();
212
+ handleInlineDisconnect(e) {
213
+ if (e) {
214
+ e.preventDefault();
215
+ e.stopPropagation();
216
+ }
217
+ this.handleDisconnect(e);
202
218
  }
203
- super.handleDisconnect(e);
204
- }
205
-
206
- minifyHtml(markup) {
207
- return typeof markup === 'string'
208
- ? markup.replace(/>\s+</g, '><').trim()
209
- : markup;
210
- }
211
219
  }
212
220
 
213
221
  export default WalletButton;
@@ -56,44 +56,77 @@ class WalletService {
56
56
  }
57
57
 
58
58
  /**
59
- * Initialize the wallet service
59
+ * Initialize the wallet service.
60
+ * Automatically reconnects to previously authorized wallets by default.
60
61
  * @param {Object} options - Initialization options
61
- * @param {boolean} options.autoReconnect - If true, automatically reconnect to previously authorized wallets
62
+ * @param {boolean} [options.autoReconnect=true] - Set to false to disable auto-reconnect
62
63
  * @returns {Promise<boolean>} True if initialized successfully
63
64
  */
64
65
  async initialize(options = {}) {
66
+ const { autoReconnect = true } = options;
67
+
65
68
  try {
66
69
  console.log('Initializing WalletService...');
67
70
 
68
71
  // Check if window.ethereum exists
69
- if (typeof window.ethereum !== 'undefined') {
70
- // Log that we found a wallet provider
71
- console.log('Found Ethereum provider');
72
-
73
- // Let other components know a wallet was detected
74
- this.eventBus.emit('wallet:detected');
75
-
76
- // Check if the provider is in MetaMask compatibility mode
77
- this.isMetaMask = window.ethereum.isMetaMask;
78
-
79
- // Set up event listeners for wallet changes
80
- this.setupEventListeners();
81
- } else {
72
+ if (typeof window.ethereum === 'undefined') {
82
73
  console.log('No Ethereum provider found');
83
74
  this.eventBus.emit('wallet:notdetected');
75
+ this.isInitialized = true;
76
+ return true;
84
77
  }
85
78
 
86
- // Mark as initialized
87
- this.isInitialized = true;
79
+ console.log('Found Ethereum provider');
80
+ this.eventBus.emit('wallet:detected');
81
+ this.isMetaMask = window.ethereum.isMetaMask;
82
+
83
+ // Check for previously authorized accounts (standard Web3 UX pattern)
84
+ if (autoReconnect) {
85
+ const accounts = await window.ethereum.request({ method: 'eth_accounts' });
86
+
87
+ if (accounts?.length > 0) {
88
+ console.log('[WalletService] Found authorized account:', accounts[0]);
89
+
90
+ // Detect wallet type and update internal state
91
+ const walletType = this._detectWalletType();
92
+ this.selectedWallet = walletType;
93
+ this.provider = window.ethereum;
94
+ this.connectedAddress = accounts[0];
95
+ this.connected = true;
88
96
 
89
- // Auto-reconnect if requested
90
- if (options.autoReconnect) {
91
- await this.tryAutoConnect();
97
+ // Create ethers provider and signer
98
+ this.ethersProvider = new ethers.providers.Web3Provider(window.ethereum, 'any');
99
+ this.signer = this.ethersProvider.getSigner();
100
+
101
+ // Set up event listeners
102
+ this.setupEventListeners();
103
+
104
+ // Store for future sessions
105
+ localStorage.setItem('ms2fun_lastWallet', walletType);
106
+
107
+ console.log('[WalletService] Auto-connected to:', accounts[0]);
108
+
109
+ // Emit connected event
110
+ this.eventBus.emit('wallet:connected', {
111
+ address: this.connectedAddress,
112
+ walletType: this.selectedWallet,
113
+ provider: this.provider,
114
+ ethersProvider: this.ethersProvider,
115
+ signer: this.signer
116
+ });
117
+ }
92
118
  }
93
119
 
120
+ // Set up listeners even if not connected (for future connections)
121
+ if (!this.connected) {
122
+ this.setupEventListeners();
123
+ }
124
+
125
+ this.isInitialized = true;
94
126
  return true;
95
127
  } catch (error) {
96
128
  console.error('Error initializing WalletService:', error);
129
+ this.isInitialized = true; // Still mark as initialized
97
130
  throw error;
98
131
  }
99
132
  }