@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,4 +1,4 @@
1
- import { Component } from '@monygroupcorp/microact';
1
+ import { Component, h, render } from '@monygroupcorp/microact';
2
2
  import { TransactionOptions } from './TransactionOptions.js';
3
3
  import { MessagePopup } from '../Util/MessagePopup.js';
4
4
  import { PriceDisplay } from '../Display/PriceDisplay.js';
@@ -9,7 +9,7 @@ import SwapButton from './SwapButton.js';
9
9
  export class SwapInterface extends Component {
10
10
  constructor(props) {
11
11
  super(props);
12
-
12
+
13
13
  this.blockchainService = props.blockchainService;
14
14
  this.eventBus = props.eventBus;
15
15
 
@@ -21,53 +21,29 @@ export class SwapInterface extends Component {
21
21
  freeMint: props.freeMint || false,
22
22
  freeSupply: props.freeSupply || 0,
23
23
  calculatingAmount: false,
24
- isPhase2: props.isPhase2 === null ? null : props.isPhase2, // null means phase is not yet determined
24
+ isPhase2: props.isPhase2 === null ? null : props.isPhase2,
25
25
  dataReady: props.dataReady || false,
26
26
  balances: props.balances || { eth: '0', exec: '0' },
27
27
  price: props.price || { current: 0 },
28
28
  contractData: props.contractData || {},
29
29
  };
30
-
31
- // Store the address - could be a promise or a direct value
30
+
32
31
  this._address = props.address || null;
33
-
34
- // Initialize child components
35
- this.transactionOptions = new TransactionOptions();
36
32
  this.messagePopup = new MessagePopup('status-message');
37
- this.priceDisplay = new PriceDisplay({ price: this.state.price, contractData: this.state.contractData });
38
- this.messagePopup.initialize();
39
-
40
- this.swapInputs = null; // Initialized in initializeChildComponents
41
-
42
- this.swapButton = new SwapButton({
43
- direction: this.state.direction,
44
- disabled: false,
45
- onClick: this.handleSwap.bind(this)
46
- });
47
-
33
+ this.messagePopup.initialize?.();
48
34
  this.calculateTimer = null;
49
-
50
- // Bind event handlers that are passed in props
51
35
  this.onDirectionChange = props.onDirectionChange;
52
-
53
- this.handleTransactionEvents = this.handleTransactionEvents.bind(this);
54
- this.handleBalanceUpdate = this.handleBalanceUpdate.bind(this);
55
- this.handleTransactionOptionsUpdate = this.handleTransactionOptionsUpdate.bind(this);
56
-
57
36
  this.transactionOptionsState = {
58
37
  message: '',
59
38
  nftMintingEnabled: false
60
39
  };
61
-
62
40
  this.approveModal = null;
63
41
  this.eventListeners = [];
64
42
  this.instanceId = Math.random().toString(36).substring(2, 9);
65
- console.log(`🔵 SwapInterface instance created: ${this.instanceId}`);
43
+ console.log(`[SwapInterface] Instance created: ${this.instanceId}`);
66
44
  }
67
45
 
68
- // Add new method to handle balance updates
69
46
  handleBalanceUpdate(update) {
70
- // Update state from the event detail or props
71
47
  const newBalances = update.balances || this.props.balances;
72
48
  const newFreeMint = update.freeMint !== undefined ? update.freeMint : this.props.freeMint;
73
49
  const newFreeSupply = update.freeSupply !== undefined ? update.freeSupply : this.props.freeSupply;
@@ -77,83 +53,34 @@ export class SwapInterface extends Component {
77
53
  freeMint: newFreeMint,
78
54
  freeSupply: newFreeSupply
79
55
  });
80
-
81
- // Update swap inputs component with new balance info directly
82
- if (this.swapInputs) {
83
- this.swapInputs.updateProps({
84
- freeMint: newFreeMint
85
- });
86
- }
87
- }
88
-
89
- updateElements() {
90
- // Update child components with new props
91
- // Use requestAnimationFrame to batch updates and prevent multiple renders
92
- requestAnimationFrame(() => {
93
- if (this.swapInputs) {
94
- this.swapInputs.updateProps({
95
- direction: this.state.direction,
96
- ethAmount: this.state.ethAmount,
97
- execAmount: this.state.execAmount,
98
- calculatingAmount: this.state.calculatingAmount,
99
- freeMint: this.state.freeMint,
100
- isPhase2: this.state.isPhase2
101
- });
102
- }
103
-
104
- if (this.swapButton) {
105
- this.swapButton.updateProps({
106
- direction: this.state.direction
107
- });
108
- }
109
- });
110
56
  }
111
57
 
112
58
  async calculateSwapAmount(amount, inputType) {
113
- // Handle empty or invalid input
114
59
  if (!amount || isNaN(parseFloat(amount))) {
115
60
  return '';
116
61
  }
117
62
 
118
63
  try {
119
64
  if (this.isLiquidityDeployed()) {
120
- // Phase 2: Use Uniswap-style calculations
121
65
  const price = this.state.price.current;
122
- console.log('calculateSwapAmount price', price);
123
-
124
66
  if (inputType === 'eth') {
125
- // Calculate EXEC amount based on ETH input
126
67
  const ethAmount = parseFloat(amount);
127
- // Apply a 5% reduction to account for 4% tax + slippage
128
68
  const execAmount = (ethAmount / price * 1000000) * 0.95;
129
- console.log('calculateSwapAmount execAmount', execAmount);
130
- return execAmount.toFixed(0); // Use integer amounts for EXEC
69
+ return execAmount.toFixed(0);
131
70
  } else {
132
- // Calculate ETH amount based on EXEC input
133
71
  const execAmount = parseFloat(amount);
134
- // Add a 5.5% buffer for 4% tax + slippage + price impact
135
72
  const ethAmount = (execAmount / 1000000) * price * 1.055;
136
- console.log('calculateSwapAmount ethAmount', ethAmount);
137
73
  return ethAmount.toFixed(6);
138
74
  }
139
75
  } else {
140
- // Phase 1: Use bonding curve logic
141
76
  if (inputType === 'eth') {
142
- // Calculate how much EXEC user will receive for their ETH
143
77
  const execAmount = await this.blockchainService.getExecForEth(amount);
144
-
145
- // Check if user is eligible for free mint
146
78
  const { freeSupply, freeMint } = this.state;
147
- console.log('calculateSwapAmount freeMint', freeMint);
148
- // If free supply is available and user hasn't claimed their free mint
149
79
  const freeMintBonus = (freeSupply > 0 && !freeMint) ? 1000000 : 0;
150
-
151
- // Round down to ensure we don't exceed maxCost
152
80
  return Math.floor(execAmount + freeMintBonus).toString();
153
81
  } else {
154
- // Calculate how much ETH user will receive for their EXEC
155
82
  const ethAmount = await this.blockchainService.getEthForExec(amount);
156
- return ethAmount.toString(); // Use more decimals for precision
83
+ return ethAmount.toString();
157
84
  }
158
85
  }
159
86
  } catch (error) {
@@ -162,311 +89,110 @@ export class SwapInterface extends Component {
162
89
  }
163
90
  }
164
91
 
165
- onMount() {
166
- console.log(`[${this.instanceId}] SwapInterface onMount called`);
92
+ didMount() {
93
+ console.log(`[${this.instanceId}] SwapInterface didMount called`);
167
94
  this.bindEvents();
168
-
95
+
169
96
  const eventSubscriptions = [
170
- ['contractData:updated', this.handleContractDataUpdate.bind(this), 'high'],
171
- ['transaction:pending', this.handleTransactionEvents, 'normal'],
172
- ['transaction:confirmed', this.handleTransactionEvents, 'normal'],
173
- ['transaction:success', this.handleTransactionEvents, 'normal'],
174
- ['transaction:error', this.handleTransactionEvents, 'normal'],
175
- ['balances:updated', this.handleBalanceUpdate, 'high'],
176
- ['transactionOptions:update', this.handleTransactionOptionsUpdate, 'low']
97
+ ['contractData:updated', this.bind(this.handleContractDataUpdate), 'high'],
98
+ ['transaction:pending', this.bind(this.handleTransactionEvents), 'normal'],
99
+ ['transaction:confirmed', this.bind(this.handleTransactionEvents), 'normal'],
100
+ ['transaction:success', this.bind(this.handleTransactionEvents), 'normal'],
101
+ ['transaction:error', this.bind(this.handleTransactionEvents), 'normal'],
102
+ ['balances:updated', this.bind(this.handleBalanceUpdate), 'high'],
103
+ ['transactionOptions:update', this.bind(this.handleTransactionOptionsUpdate), 'low']
177
104
  ];
178
-
179
- this.eventListeners = eventSubscriptions.map(([event, handler, priority]) => {
180
- console.log(`[${this.instanceId}] Subscribing to ${event} with priority ${priority}`);
105
+
106
+ this.eventListeners = eventSubscriptions.map(([event, handler]) => {
181
107
  return this.eventBus.on(event, handler);
182
108
  });
183
109
 
184
- // Check if we already have data and can determine phase immediately
185
110
  const { contractData } = this.state;
186
111
  if (contractData && contractData.liquidityPool !== undefined) {
187
112
  const isPhase2 = this.isLiquidityDeployed();
188
- this.setState({
113
+ this.setState({
189
114
  isPhase2: isPhase2,
190
115
  dataReady: true
191
116
  });
192
- this.initializeChildComponents();
193
117
  }
194
-
195
- this.update();
196
-
197
- requestAnimationFrame(() => {
198
- this.mountChildComponents();
199
- });
200
118
  }
201
119
 
202
- updateProps(newProps) {
203
- // Update state based on new props from parent
204
- const oldState = { ...this.state };
205
- const newState = {
206
- ...this.state,
207
- ...newProps
208
- };
209
-
210
- if (this.shouldUpdate(oldState, newState)) {
211
- this.setState(newState);
212
- }
213
- }
214
-
215
- /**
216
- * Initialize child components that depend on phase
217
- * This is called once phase is determined (either in onMount or handleContractDataUpdate)
218
- */
219
- initializeChildComponents() {
220
- // Only initialize if phase is known and SwapInputs hasn't been created yet
221
- if (this.state.isPhase2 === null) {
222
- console.log(`[${this.instanceId}] Phase not yet determined, skipping SwapInputs initialization`);
223
- return;
224
- }
225
-
226
- if (!this.swapInputs) {
227
- console.log(`[${this.instanceId}] Initializing SwapInputs with phase ${this.state.isPhase2 ? '2' : '1'}`);
228
- this.swapInputs = new SwapInputs({
229
- direction: this.state.direction,
230
- ethAmount: this.state.ethAmount,
231
- execAmount: this.state.execAmount,
232
- calculatingAmount: this.state.calculatingAmount,
233
- freeMint: this.state.freeMint,
234
- isPhase2: this.state.isPhase2,
235
- onInput: this.handleInput.bind(this)
236
- });
237
- }
238
- }
239
-
240
- // Add method to explicitly mount child components
241
- mountChildComponents() {
242
- // Mount price display
243
- const priceContainer = this.element.querySelector('.price-display-container');
244
- if (priceContainer && (!this.priceDisplay.element || !priceContainer.contains(this.priceDisplay.element))) {
245
- console.log(`[${this.instanceId}] Mounting PriceDisplay component`);
246
- this.priceDisplay.mount(priceContainer);
247
- }
248
-
249
- // Mount transaction options
250
- const optionsContainer = this.element.querySelector('.transaction-options-container');
251
- if (optionsContainer && this.transactionOptions &&
252
- (!this.transactionOptions.element || !optionsContainer.contains(this.transactionOptions.element))) {
253
- console.log(`[${this.instanceId}] Mounting TransactionOptions component`);
254
- this.transactionOptions.mount(optionsContainer);
255
- }
256
-
257
- // Mount swap inputs
258
- const inputsContainer = this.element.querySelector('.swap-inputs-container');
259
- if (inputsContainer && this.swapInputs) {
260
- this.swapInputs.mount(inputsContainer);
261
- }
262
-
263
- // Mount quick fill buttons
264
- const quickFillContainer = this.element.querySelector('.quick-fill-buttons-container');
265
- if (quickFillContainer) {
266
- // Create a wrapper for just the quick fill buttons
267
- quickFillContainer.innerHTML = '<div class="quick-fill-buttons"></div>';
268
- const quickFillButtons = quickFillContainer.querySelector('.quick-fill-buttons');
269
- if (quickFillButtons) {
270
- // Render quick fill buttons directly
271
- const { direction } = this.state;
272
- quickFillButtons.innerHTML = direction === 'buy' ?
273
- `<button data-amount="0.0025">0.0025</button>
274
- <button data-amount="0.01">0.01</button>
275
- <button data-amount="0.05">0.05</button>
276
- <button data-amount="0.1">0.1</button>`
277
- :
278
- `<button data-percentage="25">25%</button>
279
- <button data-percentage="50">50%</button>
280
- <button data-percentage="75">75%</button>
281
- <button data-percentage="100">100%</button>`;
282
-
283
- // Attach event listeners
284
- quickFillButtons.addEventListener('click', (e) => {
285
- if (e.target.dataset.amount || e.target.dataset.percentage) {
286
- this.handleQuickFill(e);
287
- }
288
- });
289
- }
290
- }
291
-
292
- // Mount direction switch in the slot within swap-inputs
293
- const directionSwitchSlot = this.element.querySelector('.direction-switch-slot');
294
- if (directionSwitchSlot) {
295
- directionSwitchSlot.innerHTML = '<button class="direction-switch">↑↓</button>';
296
- directionSwitchSlot.querySelector('.direction-switch').addEventListener('click', (e) => {
297
- this.handleDirectionSwitch(e);
298
- });
299
- }
300
-
301
- // Mount swap button
302
- const buttonContainer = this.element.querySelector('.swap-button-container');
303
- if (buttonContainer && this.swapButton) {
304
- this.swapButton.mount(buttonContainer);
305
- }
306
- }
307
-
308
- onUnmount() {
309
- console.log(`[${this.instanceId}] SwapInterface onUnmount called`);
310
- // Unsubscribe from all events
120
+ willUnmount() {
121
+ console.log(`[${this.instanceId}] SwapInterface willUnmount called`);
311
122
  this.eventListeners.forEach(unsubscribe => {
312
123
  if (typeof unsubscribe === 'function') {
313
124
  unsubscribe();
314
125
  }
315
126
  });
316
-
317
- // Clear the list
318
127
  this.eventListeners = [];
319
-
320
- // Remove child components
321
- if (this.transactionOptions && this.transactionOptions.element) {
322
- this.transactionOptions.element.remove();
323
- }
324
-
325
- // Ensure price display is cleaned up
326
- if (this.priceDisplay) {
327
- try {
328
- this.priceDisplay.unmount();
329
- } catch (e) {
330
- console.warn('Error unmounting price display:', e);
331
- }
332
- this.priceDisplay = null;
333
- }
334
-
335
- // Unmount sub-components
336
- if (this.swapInputs) {
337
- try {
338
- this.swapInputs.unmount();
339
- } catch (e) {
340
- console.warn('Error unmounting swap inputs:', e);
341
- }
342
- this.swapInputs = null;
343
- }
344
-
345
- // Quick fill and direction switch are handled inline, no unmount needed
346
-
347
- if (this.swapButton) {
348
- try {
349
- this.swapButton.unmount();
350
- } catch (e) {
351
- console.warn('Error unmounting swap button:', e);
352
- }
353
- this.swapButton = null;
354
- }
355
-
356
- // Make sure to close and clean up any open approval modal
128
+
357
129
  if (this.approveModal) {
358
130
  try {
359
- // Close the modal - this will trigger the approveModal:closed event
360
- // which will clean up related event listeners
361
131
  this.approveModal.handleClose();
362
132
  } catch (e) {
363
133
  console.warn('Error closing approval modal during unmount:', e);
364
134
  }
365
135
  this.approveModal = null;
366
136
  }
367
-
368
- // Clear any pending timers
137
+
369
138
  if (this.calculateTimer) {
370
139
  clearTimeout(this.calculateTimer);
371
140
  this.calculateTimer = null;
372
141
  }
373
142
  }
374
143
 
144
+ bindEvents() {
145
+ // Events are bound inline in render
146
+ }
147
+
375
148
  handleTransactionEvents(event) {
376
- console.log(`[${this.instanceId}] handleTransactionEvents called with:`, {
377
- type: event?.type,
378
- hasError: !!event?.error,
379
- hasHash: !!event?.hash,
380
- eventId: event?.id || 'none',
381
- handled: event?.handled || false
382
- });
383
-
384
- // Skip if this event has already been handled by this instance
385
149
  if (event?.handledBy?.includes(this.instanceId)) {
386
- console.log(`[${this.instanceId}] Event already handled by this instance, skipping`);
387
150
  return;
388
151
  }
389
-
390
- // Mark this event as handled by this instance
391
152
  if (!event.handledBy) {
392
153
  event.handledBy = [];
393
154
  }
394
155
  event.handledBy.push(this.instanceId);
395
-
156
+
396
157
  const direction = this.state.direction === 'buy' ? 'Buy' : 'Sell';
397
158
 
398
- // Check if this is a transaction event
399
159
  if (!event || !event.type) {
400
- console.warn('Invalid transaction event:', event);
401
160
  return;
402
161
  }
403
162
 
404
- // For transaction events - only show if it's not an error
405
163
  if ((event.type === 'buy' || event.type === 'sell' || event.type === 'swap') && !event.error) {
406
- console.log(`[${this.instanceId}] Showing transaction pending message for type:`, event.type);
407
- this.messagePopup.info(
408
- `${direction} transaction. Simulating...`,
409
- 'Transaction Pending'
410
- );
164
+ this.messagePopup.info(`${direction} transaction. Simulating...`, 'Transaction Pending');
411
165
  }
412
166
 
413
- // For confirmed transactions
414
167
  if (event.hash) {
415
- this.messagePopup.info(
416
- `Transaction confirmed, waiting for completion...`,
417
- 'Transaction Confirmed'
418
- );
168
+ this.messagePopup.info('Transaction confirmed, waiting for completion...', 'Transaction Confirmed');
419
169
  }
420
170
 
421
- // For successful transactions
422
171
  if (event.receipt && (event.type === 'buy' || event.type === 'sell' || event.type === 'swap')) {
423
- const amount = this.state.direction === 'buy'
172
+ const amount = this.state.direction === 'buy'
424
173
  ? this.state.execAmount + ' EXEC'
425
174
  : this.state.ethAmount + ' ETH';
426
-
175
+
427
176
  this.messagePopup.success(
428
177
  `Successfully ${direction.toLowerCase() === 'buy' ? 'bought' : 'sold'} ${amount}`,
429
178
  'Transaction Complete'
430
179
  );
431
180
 
432
- // Clear inputs after successful transaction
433
- this.state.ethAmount = '';
434
- this.state.execAmount = '';
435
- this.state.calculatingAmount = false;
436
-
437
- // Update child components directly
438
- if (this.swapInputs) {
439
- this.swapInputs.updateProps({
440
- ethAmount: '',
441
- execAmount: '',
442
- calculatingAmount: false
443
- });
444
- }
445
-
446
- // Re-mount child components after state update
447
- this.mountChildComponents();
181
+ this.setState({
182
+ ethAmount: '',
183
+ execAmount: '',
184
+ calculatingAmount: false
185
+ });
448
186
  }
449
187
 
450
- // For error transactions
451
188
  if (event.error && !event.handled) {
452
- console.log(`[${this.instanceId}] Handling error in handleTransactionEvents:`, event.error);
453
-
454
189
  let errorMessage = event.error?.message || 'Transaction failed';
455
-
456
190
  if (errorMessage.includes('Contract call')) {
457
191
  const parts = errorMessage.split(': ');
458
192
  errorMessage = parts[parts.length - 1];
459
193
  }
460
-
461
- const context = this.state.direction === 'buy' ?
462
- 'Buy Failed' :
463
- 'Sell Failed';
464
-
465
- this.messagePopup.error(
466
- `${context}: ${errorMessage}`,
467
- 'Transaction Failed'
468
- );
469
-
194
+ const context = this.state.direction === 'buy' ? 'Buy Failed' : 'Sell Failed';
195
+ this.messagePopup.error(`${context}: ${errorMessage}`, 'Transaction Failed');
470
196
  event.handled = true;
471
197
  }
472
198
  }
@@ -479,158 +205,99 @@ export class SwapInterface extends Component {
479
205
  }
480
206
 
481
207
  handleInput(inputType, value) {
482
- // Clear any existing timer
483
208
  if (this.calculateTimer) {
484
209
  clearTimeout(this.calculateTimer);
485
210
  }
486
211
 
487
- // Update state directly without triggering re-render
488
- // We'll update the child components directly instead
489
- this.state.activeInput = inputType;
490
- this.state.calculatingAmount = true;
491
-
212
+ const newState = {
213
+ activeInput: inputType,
214
+ calculatingAmount: true
215
+ };
216
+
492
217
  if (this.state.direction === 'buy') {
493
218
  if (inputType === 'top') {
494
- this.state.ethAmount = value;
219
+ newState.ethAmount = value;
495
220
  } else {
496
- this.state.execAmount = value;
221
+ newState.execAmount = value;
497
222
  }
498
223
  } else {
499
224
  if (inputType === 'top') {
500
- this.state.execAmount = value;
225
+ newState.execAmount = value;
501
226
  } else {
502
- this.state.ethAmount = value;
227
+ newState.ethAmount = value;
503
228
  }
504
229
  }
505
-
506
- // Update child components directly without triggering parent re-render
507
- if (this.swapInputs) {
508
- this.swapInputs.updateProps({
509
- direction: this.state.direction,
510
- ethAmount: this.state.ethAmount,
511
- execAmount: this.state.execAmount,
512
- calculatingAmount: this.state.calculatingAmount,
513
- freeMint: this.state.freeMint,
514
- isPhase2: this.state.isPhase2
515
- });
516
- }
517
230
 
518
- // Set debounced calculation
231
+ this.setState(newState);
232
+
519
233
  this.calculateTimer = setTimeout(async () => {
520
234
  try {
521
235
  const isEthInput = (this.state.direction === 'buy') === (inputType === 'top');
522
236
  const calculatedAmount = await this.calculateSwapAmount(value, isEthInput ? 'eth' : 'exec');
523
-
524
- // Update the opposite input after calculation
237
+
525
238
  if (isEthInput) {
526
- this.state.execAmount = calculatedAmount;
527
- this.state.calculatingAmount = false;
528
-
529
- // Update child component directly
530
- if (this.swapInputs) {
531
- this.swapInputs.updateProps({
532
- execAmount: calculatedAmount,
533
- calculatingAmount: false
534
- });
535
- }
239
+ this.setState({ execAmount: calculatedAmount, calculatingAmount: false });
536
240
  } else {
537
- this.state.ethAmount = calculatedAmount;
538
- this.state.calculatingAmount = false;
539
-
540
- // Update child component directly
541
- if (this.swapInputs) {
542
- this.swapInputs.updateProps({
543
- ethAmount: calculatedAmount,
544
- calculatingAmount: false
545
- });
546
- }
241
+ this.setState({ ethAmount: calculatedAmount, calculatingAmount: false });
547
242
  }
548
243
  } catch (error) {
549
244
  console.error('Error calculating swap amount:', error);
550
- this.state.calculatingAmount = false;
551
-
552
- // Update child component directly
553
- if (this.swapInputs) {
554
- this.swapInputs.updateProps({
555
- calculatingAmount: false
556
- });
557
- }
245
+ this.setState({ calculatingAmount: false });
558
246
  }
559
247
  }, 750);
560
248
  }
561
249
 
562
- events() {
563
- // Events are now handled by child components
564
- return {};
565
- }
566
-
567
- /**
568
- * Override shouldUpdate to prevent re-renders on input changes
569
- * Input changes are handled by child components directly
570
- */
571
- shouldUpdate(oldState, newState) {
572
- if (!oldState || !newState) return true;
573
- if (oldState === newState) return false;
574
-
575
- // Don't update if only input values changed (ethAmount, execAmount, activeInput, calculatingAmount)
576
- // These are handled by child components directly
577
- const inputOnlyChanges =
578
- oldState.ethAmount !== newState.ethAmount ||
579
- oldState.execAmount !== newState.execAmount ||
580
- oldState.activeInput !== newState.activeInput ||
581
- oldState.calculatingAmount !== newState.calculatingAmount;
582
-
583
- // If only input values changed, don't re-render (child components handle it)
584
- if (inputOnlyChanges &&
585
- oldState.direction === newState.direction &&
586
- oldState.freeMint === newState.freeMint &&
587
- oldState.freeSupply === newState.freeSupply &&
588
- oldState.isPhase2 === newState.isPhase2 &&
589
- oldState.dataReady === newState.dataReady) {
590
- return false;
591
- }
592
-
593
- // Update for other state changes (direction, phase, dataReady, etc.)
594
- return true;
595
- }
596
-
597
250
  handleDirectionSwitch(e) {
598
- // Prevent default button behavior and stop propagation
599
251
  if (e) {
600
252
  e.preventDefault();
601
253
  e.stopPropagation();
602
254
  }
603
-
604
- // Clear any pending calculations
255
+
605
256
  if (this.calculateTimer) {
606
257
  clearTimeout(this.calculateTimer);
607
258
  }
608
259
 
609
260
  const newDirection = this.state.direction === 'buy' ? 'sell' : 'buy';
610
-
611
- console.log('Direction Switch - Current State:', {
612
- direction: this.state.direction,
613
- newDirection,
614
- freeMint: this.state.freeMint,
615
- freeSupply: this.state.freeSupply
616
- });
617
261
 
618
- // Use setState instead of directly modifying state
619
262
  this.setState({
620
263
  direction: newDirection,
621
264
  calculatingAmount: false,
622
265
  activeInput: null
623
266
  });
624
267
 
625
- // Notify parent component of the change
626
268
  if (this.onDirectionChange) {
627
269
  this.onDirectionChange(newDirection);
628
270
  }
271
+ }
629
272
 
630
- // Update child components with new direction
631
- requestAnimationFrame(() => {
632
- this.mountChildComponents();
633
- });
273
+ handleQuickFill(amount, percentage) {
274
+ let value;
275
+
276
+ if (amount) {
277
+ value = amount;
278
+ } else if (percentage) {
279
+ const { balances } = this.state;
280
+ const execBalance = balances.exec;
281
+
282
+ if (!execBalance || execBalance === '0') {
283
+ return;
284
+ }
285
+
286
+ let readableBalance = BigInt(execBalance) / BigInt(1e18);
287
+
288
+ if (this.state.freeMint) {
289
+ readableBalance = readableBalance - BigInt(1000000);
290
+ if (readableBalance <= 0) {
291
+ this.messagePopup.info('You only have free mint tokens which cannot be sold.', 'Cannot Quick Fill');
292
+ return;
293
+ }
294
+ }
295
+
296
+ const fillAmount = (readableBalance * BigInt(percentage)) / BigInt(100);
297
+ value = fillAmount.toString();
298
+ }
299
+
300
+ this.handleInput('top', value);
634
301
  }
635
302
 
636
303
  isLiquidityDeployed() {
@@ -638,34 +305,27 @@ export class SwapInterface extends Component {
638
305
  if (!contractData || !contractData.liquidityPool) {
639
306
  return false;
640
307
  }
641
- const result = contractData.liquidityPool !== '0x0000000000000000000000000000000000000000';
642
- console.log('isLiquidityDeployed check:', {
643
- liquidityPool: contractData.liquidityPool,
644
- result: result
645
- });
646
- return result;
308
+ return contractData.liquidityPool !== '0x0000000000000000000000000000000000000000';
647
309
  }
648
310
 
649
311
  async handleSwap() {
650
312
  try {
651
- // Validate inputs
652
313
  if (this.state.calculatingAmount) {
653
314
  this.messagePopup.info('Please wait for the calculation to complete', 'Loading');
654
315
  return;
655
316
  }
656
-
317
+
657
318
  const { ethAmount, execAmount, direction, balances, freeMint } = this.state;
658
-
319
+
659
320
  if (!ethAmount || !execAmount || parseFloat(ethAmount) <= 0 || parseFloat(execAmount) <= 0) {
660
321
  this.messagePopup.info('Please enter valid amounts', 'Invalid Input');
661
322
  return;
662
323
  }
663
-
664
- // Check if user has enough balance
324
+
665
325
  if (direction === 'buy') {
666
326
  let ethBalance = parseFloat(this.blockchainService.formatEther(balances.eth || '0'));
667
327
  const ethNeeded = parseFloat(ethAmount);
668
-
328
+
669
329
  if (isNaN(ethNeeded) || isNaN(ethBalance) || ethNeeded > ethBalance) {
670
330
  this.messagePopup.info(`Not enough ETH balance. You have ${ethBalance.toFixed(6)} ETH, need ${ethNeeded} ETH`, 'Insufficient Balance');
671
331
  return;
@@ -674,7 +334,7 @@ export class SwapInterface extends Component {
674
334
  const execAmountClean = execAmount.replace(/,/g, '');
675
335
  const execBalance = BigInt(balances.exec || 0);
676
336
  const execNeeded = BigInt(parseInt(execAmountClean) || 0);
677
-
337
+
678
338
  if (execNeeded > execBalance) {
679
339
  const execBalanceFormatted = parseInt(balances.exec || 0).toLocaleString();
680
340
  const execNeededFormatted = parseInt(execAmountClean).toLocaleString();
@@ -683,12 +343,11 @@ export class SwapInterface extends Component {
683
343
  }
684
344
  }
685
345
 
686
- // Check if a free mint token is being sold
687
346
  if (direction === 'sell' && parseInt(execAmount.replace(/,/g, '')) <= 1000000 && freeMint) {
688
347
  this.messagePopup.info('Free minted tokens cannot be sold directly.', 'Free Mint Restriction');
689
348
  return;
690
349
  }
691
-
350
+
692
351
  const isLiquidityDeployed = this.isLiquidityDeployed();
693
352
  const cleanExecAmount = this.state.execAmount.replace(/,/g, '');
694
353
 
@@ -696,7 +355,7 @@ export class SwapInterface extends Component {
696
355
  const ethValue = this.blockchainService.parseEther(this.state.ethAmount);
697
356
  const execAmountBI = this.blockchainService.parseExec(cleanExecAmount);
698
357
  const address = await this.getAddress();
699
-
358
+
700
359
  if (!address) {
701
360
  this.messagePopup.error('No wallet address available. Please reconnect your wallet.', 'Wallet Error');
702
361
  return;
@@ -707,19 +366,27 @@ export class SwapInterface extends Component {
707
366
  } else {
708
367
  const routerAddress = this.blockchainService.swapRouter?.address || this.blockchainService.swapRouterAddress;
709
368
  const routerAllowance = await this.blockchainService.getApproval(address, routerAddress);
710
-
369
+
711
370
  if (BigInt(routerAllowance) < BigInt(execAmountBI)) {
712
371
  if (this.approveModal) {
713
372
  try {
714
373
  this.eventBus.off('approve:complete');
715
374
  this.approveModal.handleClose();
716
- } catch (e) { console.warn('Error closing existing approval modal:', e); }
375
+ } catch (e) { }
717
376
  this.approveModal = null;
718
377
  }
719
-
720
- this.approveModal = new ApproveModal(cleanExecAmount, this.blockchainService, address);
721
- this.approveModal.mount(document.body);
722
-
378
+
379
+ const modalRoot = document.createElement('div');
380
+ document.body.appendChild(modalRoot);
381
+
382
+ render(h(ApprovalModal, {
383
+ amount: cleanExecAmount,
384
+ blockchainService: this.blockchainService,
385
+ userAddress: address,
386
+ eventBus: this.eventBus,
387
+ ref: instance => this.approveModal = instance
388
+ }), modalRoot);
389
+
723
390
  this.eventBus.once('approve:complete', async () => {
724
391
  try {
725
392
  await this.blockchainService.swapExactTokenForEthSupportingFeeOnTransferV2(address, { amount: execAmountBI });
@@ -727,19 +394,21 @@ export class SwapInterface extends Component {
727
394
  this.messagePopup.error(`Swap Failed: ${error.message}`, 'Transaction Failed');
728
395
  }
729
396
  });
730
-
397
+
731
398
  this.eventBus.once('approveModal:closed', () => {
399
+ if (modalRoot.parentNode) {
400
+ modalRoot.parentNode.removeChild(modalRoot);
401
+ }
732
402
  this.approveModal = null;
733
403
  });
734
-
735
- this.approveModal.show();
404
+
405
+ this.approveModal?.show();
736
406
  return;
737
407
  }
738
408
 
739
409
  await this.blockchainService.swapExactTokenForEthSupportingFeeOnTransferV2(address, { amount: execAmountBI });
740
410
  }
741
411
  } else {
742
- // Bonding curve logic
743
412
  let proof;
744
413
  try {
745
414
  const currentTier = await this.blockchainService.getCurrentTier();
@@ -752,7 +421,7 @@ export class SwapInterface extends Component {
752
421
  this.messagePopup.error('Failed to verify whitelist status.', 'Whitelist Check Failed');
753
422
  return;
754
423
  }
755
-
424
+
756
425
  let adjustedExecAmount = cleanExecAmount;
757
426
  if (this.state.direction === 'buy' && this.state.freeSupply > 0 && !this.state.freeMint) {
758
427
  const numAmount = parseInt(cleanExecAmount);
@@ -792,181 +461,131 @@ export class SwapInterface extends Component {
792
461
  }
793
462
  }
794
463
 
795
- handleQuickFill(e) {
796
- e.preventDefault();
797
-
798
- const amount = e.target.dataset.amount;
799
- const percentage = e.target.dataset.percentage;
800
-
801
- let value;
802
-
803
- if (amount) {
804
- value = amount;
805
- } else if (percentage) {
806
- const { balances } = this.state;
807
- const execBalance = balances.exec;
808
-
809
- if (!execBalance || execBalance === '0') {
810
- console.warn('No EXEC balance available for quick fill');
811
- return;
812
- }
813
-
814
- let readableBalance = BigInt(execBalance) / BigInt(1e18);
815
-
816
- if (this.state.freeMint) {
817
- readableBalance = readableBalance - BigInt(1000000);
818
- if (readableBalance <= 0) {
819
- this.messagePopup.info('You only have free mint tokens which cannot be sold.', 'Cannot Quick Fill');
820
- return;
821
- }
822
- }
823
-
824
- const amount = (readableBalance * BigInt(percentage)) / BigInt(100);
825
- value = amount.toString();
826
- }
827
-
828
- this.handleInput('top', value);
829
- }
830
-
831
464
  handleContractDataUpdate() {
832
465
  try {
833
466
  const previousPhase = this.state.isPhase2;
834
467
  const wasDataReady = this.state.dataReady;
835
- const phaseWasUnknown = previousPhase === null;
836
-
837
- const { contractData } = this.state;
838
468
  const isPhase2 = this.isLiquidityDeployed();
839
-
840
- this.state.isPhase2 = isPhase2;
841
- this.state.dataReady = true;
842
-
843
- if (phaseWasUnknown && this.state.isPhase2 !== null) {
844
- console.log(`[${this.instanceId}] Phase determined: ${isPhase2 ? 'Phase 2' : 'Phase 1'}`);
845
- this.initializeChildComponents();
846
- this.setState({ dataReady: true, isPhase2 });
847
- requestAnimationFrame(() => this.mountChildComponents());
848
- return;
849
- }
850
-
851
- if (!wasDataReady && this.state.dataReady) {
852
- console.log(`[${this.instanceId}] Data ready, mounting child components`);
853
- this.setState({ dataReady: true, isPhase2 });
854
- requestAnimationFrame(() => this.mountChildComponents());
855
- return;
856
- }
857
-
858
- if (previousPhase !== this.state.isPhase2) {
859
- console.log(`Phase changed`);
860
- if (this.swapInputs) {
861
- this.swapInputs.updateProps({ isPhase2: this.state.isPhase2 });
862
- }
863
- const priceContainer = this.element.querySelector('.price-display-container');
864
- if (priceContainer && this.priceDisplay) {
865
- this.priceDisplay.mount(priceContainer);
866
- }
867
- return;
868
- }
869
-
870
- const priceContainer = this.element.querySelector('.price-display-container');
871
- if (priceContainer && this.priceDisplay) {
872
- const shouldRemount = !this.priceDisplay.element || !priceContainer.contains(this.priceDisplay.element);
873
- if (shouldRemount) {
874
- this.priceDisplay.mount(priceContainer);
875
- } else {
876
- this.priceDisplay.update();
877
- }
469
+
470
+ if (previousPhase === null || !wasDataReady) {
471
+ this.setState({
472
+ isPhase2,
473
+ dataReady: true
474
+ });
475
+ } else if (previousPhase !== isPhase2) {
476
+ this.setState({ isPhase2 });
878
477
  }
879
478
  } catch (error) {
880
479
  console.error('Error in handleContractDataUpdate:', error);
881
- if (!this.state.dataReady) {
882
- this.state.dataReady = true;
883
- this.state.isPhase2 = this.isLiquidityDeployed();
884
- if (!this.element || !this.element.innerHTML) {
885
- this.update();
886
- }
887
- }
888
480
  }
889
481
  }
890
482
 
891
- render() {
892
- console.log('🎨 SwapInterface.render - Starting render');
893
- const { direction, isPhase2, dataReady, balances, freeMint, freeSupply } = this.state;
894
-
895
- if (!dataReady || isPhase2 === null) {
896
- return `
897
- <div class="price-display-container"></div>
898
- <div class="quick-fill-buttons-container"></div>
899
- <div class="swap-inputs-container">
900
- <div style="padding: 20px; text-align: center;">Loading swap interface...</div>
901
- </div>
902
- <div class="transaction-options-container"></div>
903
- <div class="swap-button-container"></div>
904
- `;
905
- }
906
-
907
- console.log('Render - Current State:', { direction, freeMint, freeSupply, isPhase2 });
908
-
909
- const formattedEthBalance = parseFloat(balances.eth).toFixed(6);
910
- const formattedExecBalance = parseInt(balances.exec).toLocaleString();
911
- const availableExecBalance = direction === 'sell' && freeMint
912
- ? `Available: ${(parseInt(balances.exec) - 1000000).toLocaleString()}`
913
- : `Balance: ${formattedExecBalance}`;
914
-
915
- const result = `
916
- <div class="price-display-container"></div>
917
- ${direction === 'sell' && freeMint && !isPhase2 ?
918
- `<div class="free-mint-notice">You have 1,000,000 $EXEC you received for free that cannot be sold here.</div>`
919
- : direction === 'buy' && freeSupply > 0 && !freeMint && !isPhase2 ?
920
- `<div class="free-mint-notice free-mint-bonus">1,000,000 $EXEC will be added to your purchase. Thank you.</div>`
921
- : ''
922
- }
923
- <div class="quick-fill-buttons-container"></div>
924
- <div class="swap-inputs-container"></div>
925
- <div class="transaction-options-container"></div>
926
- <div class="swap-button-container"></div>
927
- `;
928
- console.log('🎨 SwapInterface.render - Completed render');
929
- return result;
930
- }
931
-
932
- /**
933
- * Get the user's wallet address, resolving any promise if needed
934
- * @returns {Promise<string>} Resolved address
935
- */
936
483
  async getAddress() {
937
484
  try {
938
- // Resolve the address if it's a Promise
939
- const resolvedAddress = await Promise.resolve(this._address);
940
-
941
- // Log the resolved address for debugging
942
- console.log(`[${this.instanceId}] Resolved address: ${resolvedAddress}`);
943
-
944
- return resolvedAddress;
485
+ return await Promise.resolve(this._address);
945
486
  } catch (error) {
946
487
  console.error(`[${this.instanceId}] Error resolving address:`, error);
947
488
  return null;
948
489
  }
949
490
  }
950
-
951
- /**
952
- * Update the user's wallet address
953
- * @param {string} newAddress - The new wallet address
954
- */
491
+
955
492
  setAddress(newAddress) {
956
493
  this._address = newAddress;
957
494
  }
958
-
959
- /**
960
- * Property to maintain backward compatibility with old code
961
- */
495
+
962
496
  get address() {
963
497
  return this._address;
964
498
  }
965
-
966
- /**
967
- * Property setter to maintain backward compatibility
968
- */
499
+
969
500
  set address(newAddress) {
970
501
  this._address = newAddress;
971
502
  }
972
- }
503
+
504
+ render() {
505
+ const { direction, isPhase2, dataReady, balances, freeMint, freeSupply, ethAmount, execAmount, calculatingAmount } = this.state;
506
+
507
+ if (!dataReady || isPhase2 === null) {
508
+ return h('div', { className: 'swap-interface' },
509
+ h('div', { className: 'price-display-container' }),
510
+ h('div', { className: 'quick-fill-buttons-container' }),
511
+ h('div', { className: 'swap-inputs-container', style: { padding: '20px', textAlign: 'center' } },
512
+ 'Loading swap interface...'
513
+ ),
514
+ h('div', { className: 'transaction-options-container' }),
515
+ h('div', { className: 'swap-button-container' })
516
+ );
517
+ }
518
+
519
+ const formattedEthBalance = parseFloat(balances.eth).toFixed(6);
520
+ const formattedExecBalance = parseInt(balances.exec).toLocaleString();
521
+
522
+ return h('div', { className: 'swap-interface' },
523
+ h(PriceDisplay, {
524
+ price: this.state.price,
525
+ contractData: this.state.contractData,
526
+ eventBus: this.eventBus
527
+ }),
528
+
529
+ // Free mint notice
530
+ direction === 'sell' && freeMint && !isPhase2 &&
531
+ h('div', { className: 'free-mint-notice' },
532
+ 'You have 1,000,000 $EXEC you received for free that cannot be sold here.'
533
+ ),
534
+ direction === 'buy' && freeSupply > 0 && !freeMint && !isPhase2 &&
535
+ h('div', { className: 'free-mint-notice free-mint-bonus' },
536
+ '1,000,000 $EXEC will be added to your purchase. Thank you.'
537
+ ),
538
+
539
+ // Quick fill buttons
540
+ h('div', { className: 'quick-fill-buttons-container' },
541
+ h('div', { className: 'quick-fill-buttons' },
542
+ direction === 'buy' ? [
543
+ h('button', { key: '0.0025', onClick: () => this.handleQuickFill('0.0025') }, '0.0025'),
544
+ h('button', { key: '0.01', onClick: () => this.handleQuickFill('0.01') }, '0.01'),
545
+ h('button', { key: '0.05', onClick: () => this.handleQuickFill('0.05') }, '0.05'),
546
+ h('button', { key: '0.1', onClick: () => this.handleQuickFill('0.1') }, '0.1')
547
+ ] : [
548
+ h('button', { key: '25', onClick: () => this.handleQuickFill(null, 25) }, '25%'),
549
+ h('button', { key: '50', onClick: () => this.handleQuickFill(null, 50) }, '50%'),
550
+ h('button', { key: '75', onClick: () => this.handleQuickFill(null, 75) }, '75%'),
551
+ h('button', { key: '100', onClick: () => this.handleQuickFill(null, 100) }, '100%')
552
+ ]
553
+ )
554
+ ),
555
+
556
+ // Swap inputs
557
+ h(SwapInputs, {
558
+ direction,
559
+ ethAmount,
560
+ execAmount,
561
+ calculatingAmount,
562
+ freeMint,
563
+ balances,
564
+ isPhase2,
565
+ onInput: this.bind(this.handleInput)
566
+ }),
567
+
568
+ // Direction switch
569
+ h('div', { className: 'direction-switch-container' },
570
+ h('button', {
571
+ className: 'direction-switch',
572
+ onClick: this.bind(this.handleDirectionSwitch)
573
+ }, '\u2195')
574
+ ),
575
+
576
+ // Transaction options (Phase 1 only)
577
+ !isPhase2 && h(TransactionOptions, {
578
+ eventBus: this.eventBus,
579
+ swapDirection: direction,
580
+ isPhase2
581
+ }),
582
+
583
+ // Swap button
584
+ h(SwapButton, {
585
+ direction,
586
+ disabled: calculatingAmount,
587
+ onClick: this.bind(this.handleSwap)
588
+ })
589
+ );
590
+ }
591
+ }