@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.
- package/README.md +87 -0
- package/dist/micro-web3.cjs.js +10 -0
- package/dist/micro-web3.cjs.js.map +1 -0
- package/dist/micro-web3.esm.js +10 -0
- package/dist/micro-web3.esm.js.map +1 -0
- package/dist/micro-web3.umd.js +10 -0
- package/dist/micro-web3.umd.js.map +1 -0
- package/package.json +34 -0
- package/rollup.config.cjs +36 -0
- package/src/components/BondingCurve/BondingCurve.js +296 -0
- package/src/components/Display/BalanceDisplay.js +81 -0
- package/src/components/Display/PriceDisplay.js +214 -0
- package/src/components/Ipfs/IpfsImage.js +265 -0
- package/src/components/Modal/ApprovalModal.js +398 -0
- package/src/components/Swap/SwapButton.js +81 -0
- package/src/components/Swap/SwapInputs.js +137 -0
- package/src/components/Swap/SwapInterface.js +972 -0
- package/src/components/Swap/TransactionOptions.js +238 -0
- package/src/components/Util/MessagePopup.js +159 -0
- package/src/components/Wallet/WalletModal.js +69 -0
- package/src/components/Wallet/WalletSplash.js +567 -0
- package/src/index.js +43 -0
- package/src/services/BlockchainService.js +1576 -0
- package/src/services/ContractCache.js +348 -0
- package/src/services/IpfsService.js +249 -0
- package/src/services/PriceService.js +191 -0
- package/src/services/WalletService.js +541 -0
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
import { Component } from '@monygroupcorp/microact';
|
|
2
|
+
import { TransactionOptions } from './TransactionOptions.js';
|
|
3
|
+
import { MessagePopup } from '../Util/MessagePopup.js';
|
|
4
|
+
import { PriceDisplay } from '../Display/PriceDisplay.js';
|
|
5
|
+
import { ApprovalModal } from '../Modal/ApprovalModal.js';
|
|
6
|
+
import { SwapInputs } from './SwapInputs.js';
|
|
7
|
+
import SwapButton from './SwapButton.js';
|
|
8
|
+
|
|
9
|
+
export class SwapInterface extends Component {
|
|
10
|
+
constructor(props) {
|
|
11
|
+
super(props);
|
|
12
|
+
|
|
13
|
+
this.blockchainService = props.blockchainService;
|
|
14
|
+
this.eventBus = props.eventBus;
|
|
15
|
+
|
|
16
|
+
this.state = {
|
|
17
|
+
direction: props.direction || 'buy',
|
|
18
|
+
ethAmount: '',
|
|
19
|
+
execAmount: '',
|
|
20
|
+
activeInput: null,
|
|
21
|
+
freeMint: props.freeMint || false,
|
|
22
|
+
freeSupply: props.freeSupply || 0,
|
|
23
|
+
calculatingAmount: false,
|
|
24
|
+
isPhase2: props.isPhase2 === null ? null : props.isPhase2, // null means phase is not yet determined
|
|
25
|
+
dataReady: props.dataReady || false,
|
|
26
|
+
balances: props.balances || { eth: '0', exec: '0' },
|
|
27
|
+
price: props.price || { current: 0 },
|
|
28
|
+
contractData: props.contractData || {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Store the address - could be a promise or a direct value
|
|
32
|
+
this._address = props.address || null;
|
|
33
|
+
|
|
34
|
+
// Initialize child components
|
|
35
|
+
this.transactionOptions = new TransactionOptions();
|
|
36
|
+
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
|
+
|
|
48
|
+
this.calculateTimer = null;
|
|
49
|
+
|
|
50
|
+
// Bind event handlers that are passed in props
|
|
51
|
+
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
|
+
this.transactionOptionsState = {
|
|
58
|
+
message: '',
|
|
59
|
+
nftMintingEnabled: false
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
this.approveModal = null;
|
|
63
|
+
this.eventListeners = [];
|
|
64
|
+
this.instanceId = Math.random().toString(36).substring(2, 9);
|
|
65
|
+
console.log(`🔵 SwapInterface instance created: ${this.instanceId}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add new method to handle balance updates
|
|
69
|
+
handleBalanceUpdate(update) {
|
|
70
|
+
// Update state from the event detail or props
|
|
71
|
+
const newBalances = update.balances || this.props.balances;
|
|
72
|
+
const newFreeMint = update.freeMint !== undefined ? update.freeMint : this.props.freeMint;
|
|
73
|
+
const newFreeSupply = update.freeSupply !== undefined ? update.freeSupply : this.props.freeSupply;
|
|
74
|
+
|
|
75
|
+
this.setState({
|
|
76
|
+
balances: newBalances,
|
|
77
|
+
freeMint: newFreeMint,
|
|
78
|
+
freeSupply: newFreeSupply
|
|
79
|
+
});
|
|
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
|
+
}
|
|
111
|
+
|
|
112
|
+
async calculateSwapAmount(amount, inputType) {
|
|
113
|
+
// Handle empty or invalid input
|
|
114
|
+
if (!amount || isNaN(parseFloat(amount))) {
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
if (this.isLiquidityDeployed()) {
|
|
120
|
+
// Phase 2: Use Uniswap-style calculations
|
|
121
|
+
const price = this.state.price.current;
|
|
122
|
+
console.log('calculateSwapAmount price', price);
|
|
123
|
+
|
|
124
|
+
if (inputType === 'eth') {
|
|
125
|
+
// Calculate EXEC amount based on ETH input
|
|
126
|
+
const ethAmount = parseFloat(amount);
|
|
127
|
+
// Apply a 5% reduction to account for 4% tax + slippage
|
|
128
|
+
const execAmount = (ethAmount / price * 1000000) * 0.95;
|
|
129
|
+
console.log('calculateSwapAmount execAmount', execAmount);
|
|
130
|
+
return execAmount.toFixed(0); // Use integer amounts for EXEC
|
|
131
|
+
} else {
|
|
132
|
+
// Calculate ETH amount based on EXEC input
|
|
133
|
+
const execAmount = parseFloat(amount);
|
|
134
|
+
// Add a 5.5% buffer for 4% tax + slippage + price impact
|
|
135
|
+
const ethAmount = (execAmount / 1000000) * price * 1.055;
|
|
136
|
+
console.log('calculateSwapAmount ethAmount', ethAmount);
|
|
137
|
+
return ethAmount.toFixed(6);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// Phase 1: Use bonding curve logic
|
|
141
|
+
if (inputType === 'eth') {
|
|
142
|
+
// Calculate how much EXEC user will receive for their ETH
|
|
143
|
+
const execAmount = await this.blockchainService.getExecForEth(amount);
|
|
144
|
+
|
|
145
|
+
// Check if user is eligible for free mint
|
|
146
|
+
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
|
+
const freeMintBonus = (freeSupply > 0 && !freeMint) ? 1000000 : 0;
|
|
150
|
+
|
|
151
|
+
// Round down to ensure we don't exceed maxCost
|
|
152
|
+
return Math.floor(execAmount + freeMintBonus).toString();
|
|
153
|
+
} else {
|
|
154
|
+
// Calculate how much ETH user will receive for their EXEC
|
|
155
|
+
const ethAmount = await this.blockchainService.getEthForExec(amount);
|
|
156
|
+
return ethAmount.toString(); // Use more decimals for precision
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Error calculating swap amount:', error);
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onMount() {
|
|
166
|
+
console.log(`[${this.instanceId}] SwapInterface onMount called`);
|
|
167
|
+
this.bindEvents();
|
|
168
|
+
|
|
169
|
+
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']
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
this.eventListeners = eventSubscriptions.map(([event, handler, priority]) => {
|
|
180
|
+
console.log(`[${this.instanceId}] Subscribing to ${event} with priority ${priority}`);
|
|
181
|
+
return this.eventBus.on(event, handler);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Check if we already have data and can determine phase immediately
|
|
185
|
+
const { contractData } = this.state;
|
|
186
|
+
if (contractData && contractData.liquidityPool !== undefined) {
|
|
187
|
+
const isPhase2 = this.isLiquidityDeployed();
|
|
188
|
+
this.setState({
|
|
189
|
+
isPhase2: isPhase2,
|
|
190
|
+
dataReady: true
|
|
191
|
+
});
|
|
192
|
+
this.initializeChildComponents();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.update();
|
|
196
|
+
|
|
197
|
+
requestAnimationFrame(() => {
|
|
198
|
+
this.mountChildComponents();
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
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
|
|
311
|
+
this.eventListeners.forEach(unsubscribe => {
|
|
312
|
+
if (typeof unsubscribe === 'function') {
|
|
313
|
+
unsubscribe();
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Clear the list
|
|
318
|
+
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
|
|
357
|
+
if (this.approveModal) {
|
|
358
|
+
try {
|
|
359
|
+
// Close the modal - this will trigger the approveModal:closed event
|
|
360
|
+
// which will clean up related event listeners
|
|
361
|
+
this.approveModal.handleClose();
|
|
362
|
+
} catch (e) {
|
|
363
|
+
console.warn('Error closing approval modal during unmount:', e);
|
|
364
|
+
}
|
|
365
|
+
this.approveModal = null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Clear any pending timers
|
|
369
|
+
if (this.calculateTimer) {
|
|
370
|
+
clearTimeout(this.calculateTimer);
|
|
371
|
+
this.calculateTimer = null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
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
|
+
if (event?.handledBy?.includes(this.instanceId)) {
|
|
386
|
+
console.log(`[${this.instanceId}] Event already handled by this instance, skipping`);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Mark this event as handled by this instance
|
|
391
|
+
if (!event.handledBy) {
|
|
392
|
+
event.handledBy = [];
|
|
393
|
+
}
|
|
394
|
+
event.handledBy.push(this.instanceId);
|
|
395
|
+
|
|
396
|
+
const direction = this.state.direction === 'buy' ? 'Buy' : 'Sell';
|
|
397
|
+
|
|
398
|
+
// Check if this is a transaction event
|
|
399
|
+
if (!event || !event.type) {
|
|
400
|
+
console.warn('Invalid transaction event:', event);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// For transaction events - only show if it's not an error
|
|
405
|
+
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
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// For confirmed transactions
|
|
414
|
+
if (event.hash) {
|
|
415
|
+
this.messagePopup.info(
|
|
416
|
+
`Transaction confirmed, waiting for completion...`,
|
|
417
|
+
'Transaction Confirmed'
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// For successful transactions
|
|
422
|
+
if (event.receipt && (event.type === 'buy' || event.type === 'sell' || event.type === 'swap')) {
|
|
423
|
+
const amount = this.state.direction === 'buy'
|
|
424
|
+
? this.state.execAmount + ' EXEC'
|
|
425
|
+
: this.state.ethAmount + ' ETH';
|
|
426
|
+
|
|
427
|
+
this.messagePopup.success(
|
|
428
|
+
`Successfully ${direction.toLowerCase() === 'buy' ? 'bought' : 'sold'} ${amount}`,
|
|
429
|
+
'Transaction Complete'
|
|
430
|
+
);
|
|
431
|
+
|
|
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();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// For error transactions
|
|
451
|
+
if (event.error && !event.handled) {
|
|
452
|
+
console.log(`[${this.instanceId}] Handling error in handleTransactionEvents:`, event.error);
|
|
453
|
+
|
|
454
|
+
let errorMessage = event.error?.message || 'Transaction failed';
|
|
455
|
+
|
|
456
|
+
if (errorMessage.includes('Contract call')) {
|
|
457
|
+
const parts = errorMessage.split(': ');
|
|
458
|
+
errorMessage = parts[parts.length - 1];
|
|
459
|
+
}
|
|
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
|
+
|
|
470
|
+
event.handled = true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
handleTransactionOptionsUpdate(options) {
|
|
475
|
+
this.transactionOptionsState = {
|
|
476
|
+
message: options.message,
|
|
477
|
+
nftMintingEnabled: options.nftMintingEnabled
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
handleInput(inputType, value) {
|
|
482
|
+
// Clear any existing timer
|
|
483
|
+
if (this.calculateTimer) {
|
|
484
|
+
clearTimeout(this.calculateTimer);
|
|
485
|
+
}
|
|
486
|
+
|
|
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
|
+
|
|
492
|
+
if (this.state.direction === 'buy') {
|
|
493
|
+
if (inputType === 'top') {
|
|
494
|
+
this.state.ethAmount = value;
|
|
495
|
+
} else {
|
|
496
|
+
this.state.execAmount = value;
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
if (inputType === 'top') {
|
|
500
|
+
this.state.execAmount = value;
|
|
501
|
+
} else {
|
|
502
|
+
this.state.ethAmount = value;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
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
|
+
|
|
518
|
+
// Set debounced calculation
|
|
519
|
+
this.calculateTimer = setTimeout(async () => {
|
|
520
|
+
try {
|
|
521
|
+
const isEthInput = (this.state.direction === 'buy') === (inputType === 'top');
|
|
522
|
+
const calculatedAmount = await this.calculateSwapAmount(value, isEthInput ? 'eth' : 'exec');
|
|
523
|
+
|
|
524
|
+
// Update the opposite input after calculation
|
|
525
|
+
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
|
+
}
|
|
536
|
+
} 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
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch (error) {
|
|
549
|
+
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
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}, 750);
|
|
560
|
+
}
|
|
561
|
+
|
|
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
|
+
handleDirectionSwitch(e) {
|
|
598
|
+
// Prevent default button behavior and stop propagation
|
|
599
|
+
if (e) {
|
|
600
|
+
e.preventDefault();
|
|
601
|
+
e.stopPropagation();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Clear any pending calculations
|
|
605
|
+
if (this.calculateTimer) {
|
|
606
|
+
clearTimeout(this.calculateTimer);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
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
|
+
|
|
618
|
+
// Use setState instead of directly modifying state
|
|
619
|
+
this.setState({
|
|
620
|
+
direction: newDirection,
|
|
621
|
+
calculatingAmount: false,
|
|
622
|
+
activeInput: null
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Notify parent component of the change
|
|
626
|
+
if (this.onDirectionChange) {
|
|
627
|
+
this.onDirectionChange(newDirection);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Update child components with new direction
|
|
631
|
+
requestAnimationFrame(() => {
|
|
632
|
+
this.mountChildComponents();
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
isLiquidityDeployed() {
|
|
637
|
+
const { contractData } = this.state;
|
|
638
|
+
if (!contractData || !contractData.liquidityPool) {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
const result = contractData.liquidityPool !== '0x0000000000000000000000000000000000000000';
|
|
642
|
+
console.log('isLiquidityDeployed check:', {
|
|
643
|
+
liquidityPool: contractData.liquidityPool,
|
|
644
|
+
result: result
|
|
645
|
+
});
|
|
646
|
+
return result;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async handleSwap() {
|
|
650
|
+
try {
|
|
651
|
+
// Validate inputs
|
|
652
|
+
if (this.state.calculatingAmount) {
|
|
653
|
+
this.messagePopup.info('Please wait for the calculation to complete', 'Loading');
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const { ethAmount, execAmount, direction, balances, freeMint } = this.state;
|
|
658
|
+
|
|
659
|
+
if (!ethAmount || !execAmount || parseFloat(ethAmount) <= 0 || parseFloat(execAmount) <= 0) {
|
|
660
|
+
this.messagePopup.info('Please enter valid amounts', 'Invalid Input');
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Check if user has enough balance
|
|
665
|
+
if (direction === 'buy') {
|
|
666
|
+
let ethBalance = parseFloat(this.blockchainService.formatEther(balances.eth || '0'));
|
|
667
|
+
const ethNeeded = parseFloat(ethAmount);
|
|
668
|
+
|
|
669
|
+
if (isNaN(ethNeeded) || isNaN(ethBalance) || ethNeeded > ethBalance) {
|
|
670
|
+
this.messagePopup.info(`Not enough ETH balance. You have ${ethBalance.toFixed(6)} ETH, need ${ethNeeded} ETH`, 'Insufficient Balance');
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
const execAmountClean = execAmount.replace(/,/g, '');
|
|
675
|
+
const execBalance = BigInt(balances.exec || 0);
|
|
676
|
+
const execNeeded = BigInt(parseInt(execAmountClean) || 0);
|
|
677
|
+
|
|
678
|
+
if (execNeeded > execBalance) {
|
|
679
|
+
const execBalanceFormatted = parseInt(balances.exec || 0).toLocaleString();
|
|
680
|
+
const execNeededFormatted = parseInt(execAmountClean).toLocaleString();
|
|
681
|
+
this.messagePopup.info(`Not enough EXEC balance. You have ${execBalanceFormatted} EXEC, need ${execNeededFormatted} EXEC`, 'Insufficient Balance');
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Check if a free mint token is being sold
|
|
687
|
+
if (direction === 'sell' && parseInt(execAmount.replace(/,/g, '')) <= 1000000 && freeMint) {
|
|
688
|
+
this.messagePopup.info('Free minted tokens cannot be sold directly.', 'Free Mint Restriction');
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const isLiquidityDeployed = this.isLiquidityDeployed();
|
|
693
|
+
const cleanExecAmount = this.state.execAmount.replace(/,/g, '');
|
|
694
|
+
|
|
695
|
+
if (isLiquidityDeployed) {
|
|
696
|
+
const ethValue = this.blockchainService.parseEther(this.state.ethAmount);
|
|
697
|
+
const execAmountBI = this.blockchainService.parseExec(cleanExecAmount);
|
|
698
|
+
const address = await this.getAddress();
|
|
699
|
+
|
|
700
|
+
if (!address) {
|
|
701
|
+
this.messagePopup.error('No wallet address available. Please reconnect your wallet.', 'Wallet Error');
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (this.state.direction === 'buy') {
|
|
706
|
+
await this.blockchainService.swapExactEthForTokenSupportingFeeOnTransfer(address, { amount: execAmountBI }, ethValue);
|
|
707
|
+
} else {
|
|
708
|
+
const routerAddress = this.blockchainService.swapRouter?.address || this.blockchainService.swapRouterAddress;
|
|
709
|
+
const routerAllowance = await this.blockchainService.getApproval(address, routerAddress);
|
|
710
|
+
|
|
711
|
+
if (BigInt(routerAllowance) < BigInt(execAmountBI)) {
|
|
712
|
+
if (this.approveModal) {
|
|
713
|
+
try {
|
|
714
|
+
this.eventBus.off('approve:complete');
|
|
715
|
+
this.approveModal.handleClose();
|
|
716
|
+
} catch (e) { console.warn('Error closing existing approval modal:', e); }
|
|
717
|
+
this.approveModal = null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
this.approveModal = new ApproveModal(cleanExecAmount, this.blockchainService, address);
|
|
721
|
+
this.approveModal.mount(document.body);
|
|
722
|
+
|
|
723
|
+
this.eventBus.once('approve:complete', async () => {
|
|
724
|
+
try {
|
|
725
|
+
await this.blockchainService.swapExactTokenForEthSupportingFeeOnTransferV2(address, { amount: execAmountBI });
|
|
726
|
+
} catch (error) {
|
|
727
|
+
this.messagePopup.error(`Swap Failed: ${error.message}`, 'Transaction Failed');
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
this.eventBus.once('approveModal:closed', () => {
|
|
732
|
+
this.approveModal = null;
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
this.approveModal.show();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
await this.blockchainService.swapExactTokenForEthSupportingFeeOnTransferV2(address, { amount: execAmountBI });
|
|
740
|
+
}
|
|
741
|
+
} else {
|
|
742
|
+
// Bonding curve logic
|
|
743
|
+
let proof;
|
|
744
|
+
try {
|
|
745
|
+
const currentTier = await this.blockchainService.getCurrentTier();
|
|
746
|
+
proof = await this.blockchainService.getMerkleProof(this.address, currentTier);
|
|
747
|
+
if (!proof) {
|
|
748
|
+
this.messagePopup.error(`You are not whitelisted for Tier ${currentTier + 1}.`, 'Not Whitelisted');
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
} catch (error) {
|
|
752
|
+
this.messagePopup.error('Failed to verify whitelist status.', 'Whitelist Check Failed');
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
let adjustedExecAmount = cleanExecAmount;
|
|
757
|
+
if (this.state.direction === 'buy' && this.state.freeSupply > 0 && !this.state.freeMint) {
|
|
758
|
+
const numAmount = parseInt(cleanExecAmount);
|
|
759
|
+
adjustedExecAmount = Math.max(0, numAmount - 1000000).toString();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const ethValue = this.blockchainService.parseEther(this.state.ethAmount);
|
|
763
|
+
const execAmountBI = this.blockchainService.parseExec(adjustedExecAmount);
|
|
764
|
+
|
|
765
|
+
if (this.state.direction === 'buy') {
|
|
766
|
+
await this.blockchainService.buyBonding({
|
|
767
|
+
amount: execAmountBI,
|
|
768
|
+
maxCost: ethValue,
|
|
769
|
+
mintNFT: this.transactionOptionsState.nftMintingEnabled,
|
|
770
|
+
proof: proof.proof,
|
|
771
|
+
message: this.transactionOptionsState.message
|
|
772
|
+
}, ethValue);
|
|
773
|
+
} else {
|
|
774
|
+
const minReturn = BigInt(ethValue) * BigInt(999) / BigInt(1000);
|
|
775
|
+
await this.blockchainService.sellBonding({
|
|
776
|
+
amount: execAmountBI,
|
|
777
|
+
minReturn: minReturn,
|
|
778
|
+
proof: proof.proof,
|
|
779
|
+
message: this.transactionOptionsState.message
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
} catch (error) {
|
|
784
|
+
console.error('Swap failed:', error);
|
|
785
|
+
let errorMessage = error.message;
|
|
786
|
+
if (errorMessage.includes('Contract call')) {
|
|
787
|
+
const parts = errorMessage.split(': ');
|
|
788
|
+
errorMessage = parts[parts.length - 1];
|
|
789
|
+
}
|
|
790
|
+
const context = this.state.direction === 'buy' ? 'Buy Failed' : 'Sell Failed';
|
|
791
|
+
this.messagePopup.error(`${context}: ${errorMessage}`, 'Transaction Failed');
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
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
|
+
handleContractDataUpdate() {
|
|
832
|
+
try {
|
|
833
|
+
const previousPhase = this.state.isPhase2;
|
|
834
|
+
const wasDataReady = this.state.dataReady;
|
|
835
|
+
const phaseWasUnknown = previousPhase === null;
|
|
836
|
+
|
|
837
|
+
const { contractData } = this.state;
|
|
838
|
+
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
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} catch (error) {
|
|
880
|
+
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
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
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
|
+
async getAddress() {
|
|
937
|
+
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;
|
|
945
|
+
} catch (error) {
|
|
946
|
+
console.error(`[${this.instanceId}] Error resolving address:`, error);
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Update the user's wallet address
|
|
953
|
+
* @param {string} newAddress - The new wallet address
|
|
954
|
+
*/
|
|
955
|
+
setAddress(newAddress) {
|
|
956
|
+
this._address = newAddress;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Property to maintain backward compatibility with old code
|
|
961
|
+
*/
|
|
962
|
+
get address() {
|
|
963
|
+
return this._address;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Property setter to maintain backward compatibility
|
|
968
|
+
*/
|
|
969
|
+
set address(newAddress) {
|
|
970
|
+
this._address = newAddress;
|
|
971
|
+
}
|
|
972
|
+
}
|