@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,265 @@
|
|
|
1
|
+
import { Component } from '@monygroupcorp/microact';
|
|
2
|
+
import { isIpfsUri, resolveIpfsToHttp, getAvailableGateways } from '../../services/IpfsService.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* IpfsImage Component
|
|
6
|
+
*
|
|
7
|
+
* Renders images with IPFS support, including gateway rotation on failure.
|
|
8
|
+
* Falls back gracefully if all gateways fail.
|
|
9
|
+
*
|
|
10
|
+
* Props:
|
|
11
|
+
* - src: Image URL (HTTP or IPFS)
|
|
12
|
+
* - alt: Alt text for image
|
|
13
|
+
* - className: CSS classes
|
|
14
|
+
* - style: Inline styles
|
|
15
|
+
* - loading: Loading attribute ('lazy', 'eager', etc.)
|
|
16
|
+
* - onLoad: Callback when image loads
|
|
17
|
+
* - onError: Callback when image fails
|
|
18
|
+
* - placeholder: Placeholder content while loading (optional)
|
|
19
|
+
* - errorPlaceholder: Error placeholder content (optional)
|
|
20
|
+
*/
|
|
21
|
+
export class IpfsImage extends Component {
|
|
22
|
+
constructor(props = {}) {
|
|
23
|
+
super();
|
|
24
|
+
this.props = props;
|
|
25
|
+
|
|
26
|
+
// State for gateway rotation and loading
|
|
27
|
+
this.setState({
|
|
28
|
+
gatewayIndex: 0,
|
|
29
|
+
isLoading: true,
|
|
30
|
+
hasError: false,
|
|
31
|
+
currentSrc: null
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Bind methods
|
|
35
|
+
this.handleImageLoad = this.handleImageLoad.bind(this);
|
|
36
|
+
this.handleImageError = this.handleImageError.bind(this);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get current HTTP URL based on src and gateway index
|
|
41
|
+
*/
|
|
42
|
+
getCurrentUrl() {
|
|
43
|
+
const { src } = this.props;
|
|
44
|
+
|
|
45
|
+
if (!src) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If not IPFS, return as-is
|
|
50
|
+
if (!isIpfsUri(src)) {
|
|
51
|
+
return src;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Resolve IPFS to HTTP using current gateway
|
|
55
|
+
return resolveIpfsToHttp(src, this.state.gatewayIndex);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle successful image load
|
|
60
|
+
*/
|
|
61
|
+
handleImageLoad(event) {
|
|
62
|
+
this.setState({
|
|
63
|
+
isLoading: false,
|
|
64
|
+
hasError: false
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Call user's onLoad callback if provided
|
|
68
|
+
if (this.props.onLoad) {
|
|
69
|
+
this.props.onLoad(event);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Handle image load error - try next gateway
|
|
75
|
+
*/
|
|
76
|
+
handleImageError(event) {
|
|
77
|
+
const gateways = getAvailableGateways();
|
|
78
|
+
const { src } = this.props;
|
|
79
|
+
|
|
80
|
+
// If not IPFS or out of gateways, show error
|
|
81
|
+
if (!isIpfsUri(src) || this.state.gatewayIndex >= gateways.length - 1) {
|
|
82
|
+
this.setState({
|
|
83
|
+
isLoading: false,
|
|
84
|
+
hasError: true
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Log error for debugging
|
|
88
|
+
console.error('[IpfsImage] Failed to load image:', src, {
|
|
89
|
+
triedGateways: this.state.gatewayIndex + 1,
|
|
90
|
+
totalGateways: gateways.length
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Call user's onError callback if provided
|
|
94
|
+
if (this.props.onError) {
|
|
95
|
+
this.props.onError(event);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Try next gateway
|
|
102
|
+
const nextIndex = this.state.gatewayIndex + 1;
|
|
103
|
+
this.setState({
|
|
104
|
+
gatewayIndex: nextIndex
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Update image src to trigger new load attempt
|
|
108
|
+
const img = this.element?.querySelector('img');
|
|
109
|
+
if (img) {
|
|
110
|
+
const nextUrl = resolveIpfsToHttp(src, nextIndex);
|
|
111
|
+
if (nextUrl) {
|
|
112
|
+
img.src = nextUrl;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Render component
|
|
119
|
+
*/
|
|
120
|
+
render() {
|
|
121
|
+
const {
|
|
122
|
+
src,
|
|
123
|
+
alt = '',
|
|
124
|
+
className = '',
|
|
125
|
+
style = {},
|
|
126
|
+
loading = 'lazy',
|
|
127
|
+
placeholder,
|
|
128
|
+
errorPlaceholder
|
|
129
|
+
} = this.props;
|
|
130
|
+
|
|
131
|
+
const { isLoading, hasError } = this.state;
|
|
132
|
+
|
|
133
|
+
if (!src) {
|
|
134
|
+
return '<div class="ipfs-image-empty"></div>';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const currentUrl = this.getCurrentUrl();
|
|
138
|
+
|
|
139
|
+
// Error state - show placeholder
|
|
140
|
+
if (hasError) {
|
|
141
|
+
if (errorPlaceholder) {
|
|
142
|
+
return errorPlaceholder;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Default error placeholder
|
|
146
|
+
return `
|
|
147
|
+
<div class="ipfs-image-error ${className}" style="${this.styleToString(style)}">
|
|
148
|
+
<div class="ipfs-image-error-icon">⚠️</div>
|
|
149
|
+
<div class="ipfs-image-error-text">IPFS image unavailable</div>
|
|
150
|
+
</div>
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Loading or loaded state - show image
|
|
155
|
+
return `
|
|
156
|
+
<div class="ipfs-image-container ${className}" style="${this.styleToString(style)}">
|
|
157
|
+
${isLoading && placeholder ? placeholder : ''}
|
|
158
|
+
<img
|
|
159
|
+
src="${this.escapeHtml(currentUrl || '')}"
|
|
160
|
+
alt="${this.escapeHtml(alt)}"
|
|
161
|
+
loading="${loading}"
|
|
162
|
+
class="ipfs-image ${isLoading ? 'ipfs-image-loading' : 'ipfs-image-loaded'}"
|
|
163
|
+
style="${isLoading ? 'opacity: 0;' : 'opacity: 1; transition: opacity 0.3s;'}"
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Mount component and attach event listeners
|
|
171
|
+
*/
|
|
172
|
+
mount(element) {
|
|
173
|
+
super.mount(element);
|
|
174
|
+
|
|
175
|
+
// Attach load/error handlers to img element
|
|
176
|
+
const img = this.element?.querySelector('img');
|
|
177
|
+
if (img) {
|
|
178
|
+
img.addEventListener('load', this.handleImageLoad);
|
|
179
|
+
img.addEventListener('error', this.handleImageError);
|
|
180
|
+
|
|
181
|
+
// Register cleanup
|
|
182
|
+
this.registerCleanup(() => {
|
|
183
|
+
img.removeEventListener('load', this.handleImageLoad);
|
|
184
|
+
img.removeEventListener('error', this.handleImageError);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Convert style object to string
|
|
191
|
+
*/
|
|
192
|
+
styleToString(style) {
|
|
193
|
+
if (!style || typeof style !== 'object') {
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return Object.entries(style)
|
|
198
|
+
.map(([key, value]) => {
|
|
199
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
200
|
+
return `${cssKey}: ${value};`;
|
|
201
|
+
})
|
|
202
|
+
.join(' ');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Static styles for the component
|
|
207
|
+
IpfsImage.styles = `
|
|
208
|
+
.ipfs-image-container {
|
|
209
|
+
position: relative;
|
|
210
|
+
display: inline-block;
|
|
211
|
+
width: 100%;
|
|
212
|
+
height: 100%;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.ipfs-image {
|
|
216
|
+
display: block;
|
|
217
|
+
width: 100%;
|
|
218
|
+
height: 100%;
|
|
219
|
+
object-fit: cover;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.ipfs-image-loading {
|
|
223
|
+
opacity: 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.ipfs-image-loaded {
|
|
227
|
+
opacity: 1;
|
|
228
|
+
transition: opacity 0.3s ease-in;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.ipfs-image-error {
|
|
232
|
+
display: flex;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
align-items: center;
|
|
235
|
+
justify-content: center;
|
|
236
|
+
min-height: 100px;
|
|
237
|
+
background-color: #f5f5f5;
|
|
238
|
+
color: #666;
|
|
239
|
+
padding: 1rem;
|
|
240
|
+
text-align: center;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.ipfs-image-error-icon {
|
|
244
|
+
font-size: 2rem;
|
|
245
|
+
margin-bottom: 0.5rem;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.ipfs-image-error-text {
|
|
249
|
+
font-size: 0.875rem;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.ipfs-image-empty {
|
|
253
|
+
display: block;
|
|
254
|
+
width: 100%;
|
|
255
|
+
height: 100%;
|
|
256
|
+
background-color: transparent;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* Dark mode support */
|
|
260
|
+
html[data-theme='dark'] .ipfs-image-error {
|
|
261
|
+
background-color: #2a2a2a;
|
|
262
|
+
color: #aaa;
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { Component } from '@monygroupcorp/microact';
|
|
2
|
+
import { MessagePopup } from '../Util/MessagePopup.js';
|
|
3
|
+
|
|
4
|
+
export class ApprovalModal extends Component {
|
|
5
|
+
constructor(props) {
|
|
6
|
+
super(props);
|
|
7
|
+
console.log('[DEBUG] ApproveModal constructor called with amount:', props.amount, 'and address:', props.userAddress);
|
|
8
|
+
|
|
9
|
+
this.blockchainService = props.blockchainService;
|
|
10
|
+
this.eventBus = props.eventBus;
|
|
11
|
+
this.amount = props.amount;
|
|
12
|
+
this.userAddress = props.userAddress;
|
|
13
|
+
|
|
14
|
+
this.messagePopup = new MessagePopup('approve-status');
|
|
15
|
+
this.handleApprove = this.handleApprove.bind(this);
|
|
16
|
+
this.handleClose = this.handleClose.bind(this);
|
|
17
|
+
this.isClosing = false;
|
|
18
|
+
this.modalId = Math.random().toString(36).substring(2, 9);
|
|
19
|
+
console.log(`[DEBUG] ApproveModal instance created with ID: ${this.modalId}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onMount() {
|
|
23
|
+
console.log(`[DEBUG-${this.modalId}] ApproveModal mounted to DOM`);
|
|
24
|
+
super.onMount();
|
|
25
|
+
this.setupEventListeners();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setupEventListeners() {
|
|
29
|
+
const approveButton = this.element.querySelector('.approve-button');
|
|
30
|
+
const closeButton = this.element.querySelector('.approve-modal-close');
|
|
31
|
+
const overlay = this.element.querySelector('.approve-modal-overlay');
|
|
32
|
+
|
|
33
|
+
approveButton?.addEventListener('click', this.handleApprove);
|
|
34
|
+
closeButton?.addEventListener('click', this.handleClose);
|
|
35
|
+
overlay?.addEventListener('click', (e) => {
|
|
36
|
+
if (e.target === overlay) this.handleClose();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async handleApprove() {
|
|
41
|
+
console.log(`[DEBUG-${this.modalId}] Approve button clicked`);
|
|
42
|
+
try {
|
|
43
|
+
// Disable the button immediately to prevent multiple clicks
|
|
44
|
+
const approveButton = this.element.querySelector('.approve-button');
|
|
45
|
+
if (!approveButton) {
|
|
46
|
+
console.error(`[DEBUG-${this.modalId}] Approve button not found in the modal`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`[DEBUG-${this.modalId}] Approve button found, disabling and updating text`);
|
|
51
|
+
const statusMessage = this.element.querySelector('.status-message') ||
|
|
52
|
+
document.createElement('div');
|
|
53
|
+
|
|
54
|
+
if (!statusMessage.classList.contains('status-message')) {
|
|
55
|
+
statusMessage.className = 'status-message';
|
|
56
|
+
const modalContent = this.element.querySelector('.approve-modal-content');
|
|
57
|
+
if (modalContent) {
|
|
58
|
+
console.log(`[DEBUG-${this.modalId}] Adding status message to modal content`);
|
|
59
|
+
modalContent.appendChild(statusMessage);
|
|
60
|
+
} else {
|
|
61
|
+
console.error(`[DEBUG-${this.modalId}] Modal content not found`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
statusMessage.textContent = 'Waiting for wallet confirmation...';
|
|
66
|
+
statusMessage.className = 'status-message pending';
|
|
67
|
+
|
|
68
|
+
approveButton.disabled = true;
|
|
69
|
+
approveButton.textContent = 'Approving...';
|
|
70
|
+
|
|
71
|
+
// Get user address if not provided
|
|
72
|
+
if (!this.userAddress) {
|
|
73
|
+
console.log(`[DEBUG-${this.modalId}] No user address provided, attempting to get from signer`);
|
|
74
|
+
if (this.blockchainService && this.blockchainService.signer) {
|
|
75
|
+
try {
|
|
76
|
+
this.userAddress = await this.blockchainService.signer.getAddress();
|
|
77
|
+
console.log(`[DEBUG-${this.modalId}] Retrieved user address for approval: ${this.userAddress}`);
|
|
78
|
+
} catch (addressError) {
|
|
79
|
+
console.error(`[DEBUG-${this.modalId}] Failed to get user address for approval:`, addressError);
|
|
80
|
+
throw new Error('Could not get wallet address for approval. Please reconnect your wallet.');
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
console.error(`[DEBUG-${this.modalId}] No blockchain service or signer available`);
|
|
84
|
+
throw new Error('No wallet connected. Please connect your wallet first.');
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
console.log(`[DEBUG-${this.modalId}] Using provided user address: ${this.userAddress}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Format token amount with 18 decimals
|
|
91
|
+
console.log(`[DEBUG-${this.modalId}] Parsing amount: ${this.amount}`);
|
|
92
|
+
const parsedAmount = this.blockchainService.parseExec(this.amount);
|
|
93
|
+
console.log(`[DEBUG-${this.modalId}] Parsed amount: ${parsedAmount}`);
|
|
94
|
+
|
|
95
|
+
// Get router address
|
|
96
|
+
const routerAddress = this.blockchainService.swapRouter?.address || this.blockchainService.swapRouter;
|
|
97
|
+
console.log(`[DEBUG-${this.modalId}] Router address for approval: ${routerAddress}`);
|
|
98
|
+
|
|
99
|
+
// Call the standard setApproval method
|
|
100
|
+
console.log(`[DEBUG-${this.modalId}] Approving ${this.amount} EXEC tokens from ${this.userAddress} to ${routerAddress}`);
|
|
101
|
+
|
|
102
|
+
statusMessage.textContent = 'Transaction submitted, waiting for confirmation...';
|
|
103
|
+
|
|
104
|
+
// Send the approval transaction
|
|
105
|
+
console.log(`[DEBUG-${this.modalId}] Calling blockchainService.setApproval`);
|
|
106
|
+
try {
|
|
107
|
+
const approvalResult = await this.blockchainService.setApproval(routerAddress, parsedAmount);
|
|
108
|
+
console.log(`[DEBUG-${this.modalId}] Approval transaction result:`, approvalResult);
|
|
109
|
+
} catch (txError) {
|
|
110
|
+
console.error(`[DEBUG-${this.modalId}] Transaction error:`, txError);
|
|
111
|
+
throw txError;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Update status message
|
|
115
|
+
statusMessage.textContent = 'Approval successful!';
|
|
116
|
+
statusMessage.className = 'status-message success';
|
|
117
|
+
|
|
118
|
+
// Wait briefly to show success message
|
|
119
|
+
console.log(`[DEBUG-${this.modalId}] Approval successful, waiting before emitting event`);
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
121
|
+
|
|
122
|
+
// Emit success event
|
|
123
|
+
console.log(`[DEBUG-${this.modalId}] Emitting approve:complete event`);
|
|
124
|
+
this.eventBus.emit('approve:complete');
|
|
125
|
+
|
|
126
|
+
// Close modal
|
|
127
|
+
console.log(`[DEBUG-${this.modalId}] Calling handleClose after successful approval`);
|
|
128
|
+
this.handleClose();
|
|
129
|
+
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`[DEBUG-${this.modalId}] Approval failed:`, error);
|
|
132
|
+
|
|
133
|
+
let errorMessage = error.message;
|
|
134
|
+
if (errorMessage.includes('Contract call')) {
|
|
135
|
+
const parts = errorMessage.split(': ');
|
|
136
|
+
errorMessage = parts[parts.length - 1];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Update status message in the modal
|
|
140
|
+
const statusMessage = this.element.querySelector('.status-message') ||
|
|
141
|
+
document.createElement('div');
|
|
142
|
+
|
|
143
|
+
if (!statusMessage.classList.contains('status-message')) {
|
|
144
|
+
statusMessage.className = 'status-message';
|
|
145
|
+
const modalContent = this.element.querySelector('.approve-modal-content');
|
|
146
|
+
if (modalContent) {
|
|
147
|
+
modalContent.appendChild(statusMessage);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
statusMessage.textContent = `Error: ${errorMessage}`;
|
|
152
|
+
statusMessage.className = 'status-message error';
|
|
153
|
+
|
|
154
|
+
this.messagePopup.error(
|
|
155
|
+
`Approval Failed: ${errorMessage}`,
|
|
156
|
+
'Transaction Failed'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Re-enable button
|
|
160
|
+
const approveButton = this.element.querySelector('.approve-button');
|
|
161
|
+
if (approveButton) {
|
|
162
|
+
approveButton.disabled = false;
|
|
163
|
+
approveButton.textContent = 'Approve';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
handleClose() {
|
|
169
|
+
console.log(`[DEBUG-${this.modalId}] handleClose called`);
|
|
170
|
+
// Prevent multiple close operations
|
|
171
|
+
if (this.isClosing) {
|
|
172
|
+
console.log(`[DEBUG-${this.modalId}] Already closing, skipping`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.isClosing = true;
|
|
176
|
+
|
|
177
|
+
console.log(`[DEBUG-${this.modalId}] Closing approval modal`);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
// Remove the modal from the DOM
|
|
181
|
+
if (this.element && this.element.parentNode) {
|
|
182
|
+
console.log(`[DEBUG-${this.modalId}] Removing modal from DOM`);
|
|
183
|
+
this.element.parentNode.removeChild(this.element);
|
|
184
|
+
} else {
|
|
185
|
+
console.warn(`[DEBUG-${this.modalId}] Modal element or parent not found during close`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Emit a closed event
|
|
189
|
+
console.log(`[DEBUG-${this.modalId}] Emitting approveModal:closed event`);
|
|
190
|
+
this.eventBus.emit('approveModal:closed');
|
|
191
|
+
|
|
192
|
+
// Clean up any resources
|
|
193
|
+
console.log(`[DEBUG-${this.modalId}] Calling dispose method`);
|
|
194
|
+
this.dispose();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(`[DEBUG-${this.modalId}] Error closing approval modal:`, error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Properly dispose of the component
|
|
201
|
+
dispose() {
|
|
202
|
+
console.log(`[DEBUG-${this.modalId}] Disposing component resources`);
|
|
203
|
+
|
|
204
|
+
// Remove event listeners
|
|
205
|
+
const approveButton = this.element?.querySelector('.approve-button');
|
|
206
|
+
const closeButton = this.element?.querySelector('.approve-modal-close');
|
|
207
|
+
const overlay = this.element?.querySelector('.approve-modal-overlay');
|
|
208
|
+
|
|
209
|
+
approveButton?.removeEventListener('click', this.handleApprove);
|
|
210
|
+
closeButton?.removeEventListener('click', this.handleClose);
|
|
211
|
+
|
|
212
|
+
// Clear references
|
|
213
|
+
this.isClosing = true;
|
|
214
|
+
this.blockchainService = null;
|
|
215
|
+
this.userAddress = null;
|
|
216
|
+
console.log(`[DEBUG-${this.modalId}] Component disposed`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
show() {
|
|
220
|
+
console.log(`[DEBUG-${this.modalId}] Show method called`);
|
|
221
|
+
this.isClosing = false;
|
|
222
|
+
this.element.style.display = 'block';
|
|
223
|
+
console.log(`[DEBUG-${this.modalId}] Modal set to display:block`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
hide() {
|
|
227
|
+
console.log(`[DEBUG-${this.modalId}] Hide method called`);
|
|
228
|
+
this.element.style.display = 'none';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
events() {
|
|
232
|
+
console.log(`[DEBUG-${this.modalId}] Setting up event handlers`);
|
|
233
|
+
return {
|
|
234
|
+
'click .approve-button': (e) => {
|
|
235
|
+
console.log(`[DEBUG-${this.modalId}] Approve button clicked, calling handleApprove`);
|
|
236
|
+
this.handleApprove();
|
|
237
|
+
},
|
|
238
|
+
'click .approve-modal-close': (e) => {
|
|
239
|
+
console.log(`[DEBUG-${this.modalId}] Close button clicked, calling handleClose`);
|
|
240
|
+
this.handleClose();
|
|
241
|
+
},
|
|
242
|
+
'click .approve-modal-overlay': (e) => {
|
|
243
|
+
console.log(`[DEBUG-${this.modalId}] Overlay clicked`, e.target, e.currentTarget);
|
|
244
|
+
if (e.target === e.currentTarget) {
|
|
245
|
+
console.log(`[DEBUG-${this.modalId}] Overlay direct click detected, calling handleClose`);
|
|
246
|
+
this.handleClose();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
render() {
|
|
253
|
+
// Get router address directly from the blockchain service instead of the store
|
|
254
|
+
const routerAddress = this.blockchainService.swapRouter?.address || this.blockchainService.swapRouter;
|
|
255
|
+
const formattedAmount = parseInt(this.amount).toLocaleString();
|
|
256
|
+
|
|
257
|
+
return `
|
|
258
|
+
<div class="approve-modal-overlay">
|
|
259
|
+
<div class="approve-modal">
|
|
260
|
+
<button class="approve-modal-close">×</button>
|
|
261
|
+
<div class="approve-modal-content">
|
|
262
|
+
<h2>Approve Router</h2>
|
|
263
|
+
<p>Before selling your $EXEC tokens, you need to approve the router contract to spend them.</p>
|
|
264
|
+
|
|
265
|
+
<div class="approve-details">
|
|
266
|
+
<div class="approve-info">
|
|
267
|
+
<span class="label">Amount to Approve:</span>
|
|
268
|
+
<span class="value">${formattedAmount} $EXEC</span>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="approve-info">
|
|
271
|
+
<span class="label">Router Address:</span>
|
|
272
|
+
<span class="value">${routerAddress}</span>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<button class="approve-button">
|
|
277
|
+
Approve
|
|
278
|
+
</button>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
static get styles() {
|
|
286
|
+
return `
|
|
287
|
+
.approve-modal-overlay {
|
|
288
|
+
position: fixed;
|
|
289
|
+
top: 0;
|
|
290
|
+
left: 0;
|
|
291
|
+
right: 0;
|
|
292
|
+
bottom: 0;
|
|
293
|
+
background-color: rgba(0, 0, 0, 0.75);
|
|
294
|
+
display: flex;
|
|
295
|
+
justify-content: center;
|
|
296
|
+
align-items: center;
|
|
297
|
+
z-index: 1000;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.approve-modal {
|
|
301
|
+
background-color: #111;
|
|
302
|
+
border-radius: 8px;
|
|
303
|
+
padding: 24px;
|
|
304
|
+
position: relative;
|
|
305
|
+
width: 90%;
|
|
306
|
+
max-width: 500px;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.approve-modal-close {
|
|
310
|
+
position: absolute;
|
|
311
|
+
top: 16px;
|
|
312
|
+
right: 16px;
|
|
313
|
+
background: none;
|
|
314
|
+
border: none;
|
|
315
|
+
font-size: 24px;
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
color: #fff;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.approve-modal h2 {
|
|
321
|
+
margin: 0 0 16px 0;
|
|
322
|
+
color: #fff;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.approve-details {
|
|
326
|
+
background-color: #1a1a1a;
|
|
327
|
+
border-radius: 8px;
|
|
328
|
+
padding: 16px;
|
|
329
|
+
margin: 16px 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.approve-info {
|
|
333
|
+
display: flex;
|
|
334
|
+
justify-content: space-between;
|
|
335
|
+
margin-bottom: 8px;
|
|
336
|
+
word-break: break-all;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.approve-info:last-child {
|
|
340
|
+
margin-bottom: 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.approve-info .label {
|
|
344
|
+
color: #888;
|
|
345
|
+
margin-right: 16px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.approve-info .value {
|
|
349
|
+
color: #fff;
|
|
350
|
+
text-align: right;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.approve-button {
|
|
354
|
+
width: 100%;
|
|
355
|
+
padding: 12px;
|
|
356
|
+
background-color: #007bff;
|
|
357
|
+
color: white;
|
|
358
|
+
border: none;
|
|
359
|
+
border-radius: 4px;
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
font-size: 16px;
|
|
362
|
+
margin-top: 16px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.approve-button:disabled {
|
|
366
|
+
background-color: #555;
|
|
367
|
+
cursor: not-allowed;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.approve-button:hover:not(:disabled) {
|
|
371
|
+
background-color: #0056b3;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.status-message {
|
|
375
|
+
margin-top: 12px;
|
|
376
|
+
padding: 10px;
|
|
377
|
+
border-radius: 4px;
|
|
378
|
+
text-align: center;
|
|
379
|
+
font-size: 14px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.status-message.pending {
|
|
383
|
+
background-color: #2c3e50;
|
|
384
|
+
color: #f1c40f;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.status-message.success {
|
|
388
|
+
background-color: #27ae60;
|
|
389
|
+
color: white;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.status-message.error {
|
|
393
|
+
background-color: #c0392b;
|
|
394
|
+
color: white;
|
|
395
|
+
}
|
|
396
|
+
`;
|
|
397
|
+
}
|
|
398
|
+
}export default ApprovalModal;
|