decharge-scout 1.5.0 ā 2.0.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/index.js +63 -26
- package/package.json +7 -5
- package/public/index.html +373 -0
- package/src/browser-wallet.js +195 -0
- package/src/energy-data.js +9 -1
- package/src/wallet-server.js +181 -0
package/index.js
CHANGED
|
@@ -32,7 +32,8 @@ import os from 'os';
|
|
|
32
32
|
dotenv.config();
|
|
33
33
|
|
|
34
34
|
// Import modules
|
|
35
|
-
import {
|
|
35
|
+
import { startWalletServer, openWalletConnection, waitForWalletConnection, getConnectedWallet } from './src/wallet-server.js';
|
|
36
|
+
import { setConnectedWallet, getBalance, checkBalance, mockStake, refundStake } from './src/browser-wallet.js';
|
|
36
37
|
import { fetchEnergyData, fetchElectricityMapsData } from './src/energy-data.js';
|
|
37
38
|
import { findCheapestWindow, calculateSavings } from './src/optimizer.js';
|
|
38
39
|
import { submitToOracle } from './src/oracle.js';
|
|
@@ -133,6 +134,7 @@ function findExistingWallets() {
|
|
|
133
134
|
*/
|
|
134
135
|
async function ensureWallet(walletPath) {
|
|
135
136
|
if (existsSync(walletPath)) {
|
|
137
|
+
console.log(chalk.green(`ā Using wallet: ${walletPath}`));
|
|
136
138
|
return walletPath;
|
|
137
139
|
}
|
|
138
140
|
|
|
@@ -284,14 +286,41 @@ async function main(options) {
|
|
|
284
286
|
// Auto-configure environment
|
|
285
287
|
await ensureEnvironment();
|
|
286
288
|
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
const
|
|
289
|
+
// Start wallet server
|
|
290
|
+
console.log(chalk.blue('š Starting browser wallet connection...'));
|
|
291
|
+
const server = await startWalletServer();
|
|
290
292
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
if (!server) {
|
|
294
|
+
console.log(chalk.red('ā Failed to start wallet server'));
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Open browser for wallet connection
|
|
299
|
+
await openWalletConnection();
|
|
300
|
+
|
|
301
|
+
// Wait for wallet connection
|
|
302
|
+
const walletSpinner = ora('Waiting for wallet connection in browser...').start();
|
|
303
|
+
const walletAddress = await waitForWalletConnection();
|
|
304
|
+
walletSpinner.succeed(chalk.green(`ā Wallet connected: ${walletAddress}`));
|
|
305
|
+
|
|
306
|
+
// Set connected wallet
|
|
307
|
+
setConnectedWallet(walletAddress);
|
|
308
|
+
|
|
309
|
+
// Check wallet balance
|
|
310
|
+
const balanceSpinner = ora('Checking wallet balance...').start();
|
|
311
|
+
const balance = await getBalance();
|
|
312
|
+
balanceSpinner.succeed(chalk.green(`š° Wallet Balance: ${balance.toFixed(4)} SOL`));
|
|
313
|
+
|
|
314
|
+
// Verify sufficient balance
|
|
315
|
+
try {
|
|
316
|
+
await checkBalance(STAKE_AMOUNT + 0.001); // stake + fees
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.log(chalk.red(`\nā ${error.message}`));
|
|
319
|
+
console.log(chalk.yellow(`\nPlease fund your wallet and try again:`));
|
|
320
|
+
console.log(chalk.blue(`https://faucet.solana.com/`));
|
|
321
|
+
console.log(chalk.gray(`Wallet: ${walletAddress}\n`));
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
295
324
|
|
|
296
325
|
// Set agent name
|
|
297
326
|
const agentName = options.agentName || generateAgentName();
|
|
@@ -307,25 +336,33 @@ async function main(options) {
|
|
|
307
336
|
// Ask user to confirm or override (with timeout)
|
|
308
337
|
console.log(chalk.blue('\nYou can use this location or enter a custom one.'));
|
|
309
338
|
console.log(chalk.gray('(Press Enter to use detected location, or type custom location)'));
|
|
339
|
+
console.log(chalk.gray('You have 30 seconds to respond...'));
|
|
310
340
|
|
|
311
341
|
try {
|
|
312
|
-
// Create a promise that auto-resolves after
|
|
342
|
+
// Create a promise that auto-resolves after 30 seconds
|
|
313
343
|
const timeoutPromise = new Promise((resolve) => {
|
|
314
344
|
setTimeout(() => {
|
|
315
|
-
console.log(chalk.yellow('\nā±ļø
|
|
316
|
-
resolve('');
|
|
317
|
-
},
|
|
345
|
+
console.log(chalk.yellow('\nā±ļø Timeout - using detected location...'));
|
|
346
|
+
resolve('__TIMEOUT__');
|
|
347
|
+
}, 30000);
|
|
318
348
|
});
|
|
319
349
|
|
|
320
350
|
const questionPromise = question('Enter custom location (or press Enter): ');
|
|
321
351
|
|
|
322
352
|
const customLocation = await Promise.race([questionPromise, timeoutPromise]);
|
|
323
|
-
location = customLocation.trim() || detectedLocation;
|
|
324
353
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
354
|
+
// If timeout occurred, use detected location
|
|
355
|
+
if (customLocation === '__TIMEOUT__') {
|
|
356
|
+
location = detectedLocation;
|
|
328
357
|
console.log(chalk.green(`ā Using detected location: ${location}`));
|
|
358
|
+
} else {
|
|
359
|
+
// Use custom location if provided, otherwise use detected
|
|
360
|
+
location = customLocation.trim() || detectedLocation;
|
|
361
|
+
if (customLocation.trim()) {
|
|
362
|
+
console.log(chalk.green(`ā Using custom location: ${location}`));
|
|
363
|
+
} else {
|
|
364
|
+
console.log(chalk.green(`ā Using detected location: ${location}`));
|
|
365
|
+
}
|
|
329
366
|
}
|
|
330
367
|
} catch (error) {
|
|
331
368
|
console.log(chalk.yellow(`\nā ļø Prompt error, using detected location: ${detectedLocation}`));
|
|
@@ -336,14 +373,14 @@ async function main(options) {
|
|
|
336
373
|
}
|
|
337
374
|
|
|
338
375
|
// Initialize points system
|
|
339
|
-
initializePoints(
|
|
340
|
-
const currentPoints = getPoints(
|
|
376
|
+
initializePoints(walletAddress);
|
|
377
|
+
const currentPoints = getPoints(walletAddress);
|
|
341
378
|
console.log(chalk.magenta(`ā Current Points: ${currentPoints}`));
|
|
342
379
|
|
|
343
|
-
// Stake SOL
|
|
380
|
+
// Stake SOL (via browser wallet)
|
|
344
381
|
console.log(chalk.yellow(`\nš° Staking ${STAKE_AMOUNT} SOL for anti-spam/gas...`));
|
|
345
382
|
const stakeSpinner = ora('Submitting stake transaction...').start();
|
|
346
|
-
stakeTransactionSignature = await
|
|
383
|
+
stakeTransactionSignature = await mockStake(STAKE_AMOUNT);
|
|
347
384
|
stakeSpinner.succeed(chalk.green(`Stake successful! TX: ${stakeTransactionSignature}`));
|
|
348
385
|
|
|
349
386
|
console.log(chalk.cyan('\nš Starting query cycle (runs every 15 minutes)...'));
|
|
@@ -357,14 +394,14 @@ async function main(options) {
|
|
|
357
394
|
if (totalRuns > 0) {
|
|
358
395
|
console.log(chalk.blue('šø Refunding stake...'));
|
|
359
396
|
try {
|
|
360
|
-
const refundTx = await refundStake(
|
|
397
|
+
const refundTx = await refundStake(STAKE_AMOUNT);
|
|
361
398
|
console.log(chalk.green(`Refund successful! TX: ${refundTx}`));
|
|
362
399
|
} catch (error) {
|
|
363
400
|
console.error(chalk.red(`Refund failed: ${error.message}`));
|
|
364
401
|
}
|
|
365
402
|
}
|
|
366
403
|
|
|
367
|
-
const finalPoints = getPoints(
|
|
404
|
+
const finalPoints = getPoints(walletAddress);
|
|
368
405
|
console.log(chalk.magenta(`\nā Final Points: ${finalPoints}`));
|
|
369
406
|
console.log(chalk.cyan(`š Total Runs: ${totalRuns}\n`));
|
|
370
407
|
|
|
@@ -461,7 +498,7 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
461
498
|
headers: { 'Content-Type': 'application/json' },
|
|
462
499
|
body: JSON.stringify({
|
|
463
500
|
...submissionData,
|
|
464
|
-
wallet:
|
|
501
|
+
wallet: walletAddress,
|
|
465
502
|
run_number: totalRuns
|
|
466
503
|
})
|
|
467
504
|
});
|
|
@@ -487,8 +524,8 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
487
524
|
const bonusPoints = savings > 15 ? 2 : 0; // Bonus for good savings
|
|
488
525
|
const totalPointsEarned = basePoints + bonusPoints;
|
|
489
526
|
|
|
490
|
-
awardPoints(
|
|
491
|
-
const currentPoints = getPoints(
|
|
527
|
+
awardPoints(walletAddress, totalPointsEarned);
|
|
528
|
+
const currentPoints = getPoints(walletAddress);
|
|
492
529
|
|
|
493
530
|
console.log(chalk.magenta(`\nā Earned ${totalPointsEarned} points! (${basePoints} base${bonusPoints > 0 ? ` + ${bonusPoints} bonus` : ''})`));
|
|
494
531
|
console.log(chalk.magenta(`ā Total Points: ${currentPoints}`));
|
|
@@ -497,7 +534,7 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
497
534
|
if (options.premium && totalRuns % 3 === 0) {
|
|
498
535
|
console.log(chalk.yellow('\nš Premium Feature Available!'));
|
|
499
536
|
try {
|
|
500
|
-
const premiumData = await purchasePremiumData(
|
|
537
|
+
const premiumData = await purchasePremiumData(walletAddress);
|
|
501
538
|
console.log(chalk.green(`Premium forecast data: ${JSON.stringify(premiumData)}`));
|
|
502
539
|
} catch (error) {
|
|
503
540
|
console.log(chalk.red(`Premium purchase failed: ${error.message}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "decharge-scout",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "AI-powered energy grid data scout with Solana integration",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -34,13 +34,15 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://github.com/sentinelcore/agentone#readme",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@solana/web3.js": "^1.95.8",
|
|
38
37
|
"@solana/spl-token": "^0.4.9",
|
|
38
|
+
"@solana/web3.js": "^1.95.8",
|
|
39
|
+
"chalk": "^5.3.0",
|
|
39
40
|
"commander": "^12.1.0",
|
|
41
|
+
"dotenv": "^16.4.5",
|
|
42
|
+
"express": "^5.2.1",
|
|
40
43
|
"node-fetch": "^3.3.2",
|
|
41
|
-
"
|
|
42
|
-
"ora": "^8.1.1"
|
|
43
|
-
"dotenv": "^16.4.5"
|
|
44
|
+
"open": "^11.0.0",
|
|
45
|
+
"ora": "^8.1.1"
|
|
44
46
|
},
|
|
45
47
|
"engines": {
|
|
46
48
|
"node": ">=20.0.0"
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>DeCharge Scout - Connect Wallet</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
16
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
padding: 20px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.container {
|
|
25
|
+
background: white;
|
|
26
|
+
border-radius: 20px;
|
|
27
|
+
padding: 40px;
|
|
28
|
+
max-width: 500px;
|
|
29
|
+
width: 100%;
|
|
30
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.logo {
|
|
34
|
+
text-align: center;
|
|
35
|
+
margin-bottom: 30px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.logo h1 {
|
|
39
|
+
font-size: 32px;
|
|
40
|
+
color: #667eea;
|
|
41
|
+
margin-bottom: 10px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.logo p {
|
|
45
|
+
color: #666;
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.status {
|
|
50
|
+
background: #f7f7f7;
|
|
51
|
+
border-radius: 10px;
|
|
52
|
+
padding: 20px;
|
|
53
|
+
margin-bottom: 30px;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.status.connected {
|
|
58
|
+
background: #d4edda;
|
|
59
|
+
border: 2px solid #28a745;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.status-icon {
|
|
63
|
+
font-size: 48px;
|
|
64
|
+
margin-bottom: 10px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.status-text {
|
|
68
|
+
font-size: 16px;
|
|
69
|
+
color: #333;
|
|
70
|
+
margin-bottom: 5px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.wallet-address {
|
|
74
|
+
font-family: 'Courier New', monospace;
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
color: #666;
|
|
77
|
+
word-break: break-all;
|
|
78
|
+
margin-top: 10px;
|
|
79
|
+
padding: 10px;
|
|
80
|
+
background: white;
|
|
81
|
+
border-radius: 5px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.wallet-buttons {
|
|
85
|
+
display: grid;
|
|
86
|
+
gap: 15px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.wallet-btn {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
gap: 12px;
|
|
94
|
+
padding: 16px 24px;
|
|
95
|
+
border: 2px solid #e0e0e0;
|
|
96
|
+
border-radius: 12px;
|
|
97
|
+
background: white;
|
|
98
|
+
font-size: 16px;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
color: #333;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
transition: all 0.2s;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.wallet-btn:hover {
|
|
106
|
+
border-color: #667eea;
|
|
107
|
+
background: #f8f9ff;
|
|
108
|
+
transform: translateY(-2px);
|
|
109
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.wallet-btn:active {
|
|
113
|
+
transform: translateY(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.wallet-btn img {
|
|
117
|
+
width: 24px;
|
|
118
|
+
height: 24px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.disconnect-btn {
|
|
122
|
+
width: 100%;
|
|
123
|
+
padding: 16px;
|
|
124
|
+
background: #dc3545;
|
|
125
|
+
color: white;
|
|
126
|
+
border: none;
|
|
127
|
+
border-radius: 12px;
|
|
128
|
+
font-size: 16px;
|
|
129
|
+
font-weight: 600;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
transition: all 0.2s;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.disconnect-btn:hover {
|
|
135
|
+
background: #c82333;
|
|
136
|
+
transform: translateY(-2px);
|
|
137
|
+
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.info {
|
|
141
|
+
margin-top: 20px;
|
|
142
|
+
padding: 15px;
|
|
143
|
+
background: #e7f3ff;
|
|
144
|
+
border-left: 4px solid #667eea;
|
|
145
|
+
border-radius: 5px;
|
|
146
|
+
font-size: 14px;
|
|
147
|
+
color: #333;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.loading {
|
|
151
|
+
display: inline-block;
|
|
152
|
+
width: 20px;
|
|
153
|
+
height: 20px;
|
|
154
|
+
border: 3px solid #f3f3f3;
|
|
155
|
+
border-top: 3px solid #667eea;
|
|
156
|
+
border-radius: 50%;
|
|
157
|
+
animation: spin 1s linear infinite;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@keyframes spin {
|
|
161
|
+
0% { transform: rotate(0deg); }
|
|
162
|
+
100% { transform: rotate(360deg); }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.error {
|
|
166
|
+
background: #f8d7da;
|
|
167
|
+
border-left: 4px solid #dc3545;
|
|
168
|
+
padding: 15px;
|
|
169
|
+
border-radius: 5px;
|
|
170
|
+
margin-top: 20px;
|
|
171
|
+
color: #721c24;
|
|
172
|
+
font-size: 14px;
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
175
|
+
</head>
|
|
176
|
+
<body>
|
|
177
|
+
<div class="container">
|
|
178
|
+
<div class="logo">
|
|
179
|
+
<h1>ā” DeCharge Scout</h1>
|
|
180
|
+
<p>Solana Energy Grid Optimization</p>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div class="status" id="status">
|
|
184
|
+
<div class="status-icon">š</div>
|
|
185
|
+
<div class="status-text">Connect your Solana wallet to continue</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div id="wallet-select" class="wallet-buttons">
|
|
189
|
+
<button class="wallet-btn" onclick="connectPhantom()">
|
|
190
|
+
<img src="data:image/svg+xml,%3Csvg fill='%23AB9FF2' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'%3E%3Cpath d='M107.2 12.5C95.9 4.3 78.9 0 56.7 0 18.7 0 0 21.3 0 58.6c0 24.2 10.4 41.3 31.1 51.1 11.1 5.3 19.8 6.9 37.1 6.9 11.1 0 19.1-1.6 27.7-5.5 2.9-1.3 3.7-1.6 5.8-1.6 4.5 0 8.5 3.2 8.5 6.9 0 4.8-5.3 8.5-13.9 9.6-3.2.5-9.3.8-13.9.8-17.1 0-30.4-3.2-43.7-10.9C16.9 104.6 0 84.7 0 58.6 0 21.9 22.4 0 56.7 0c30.9 0 51.1 12.8 62.9 39.7 2.4 5.3 3.5 10.1 3.5 14.4 0 10.1-5.3 17.1-13.3 17.1-4 0-7.2-1.3-9.9-4.3-2.1-2.4-2.9-4.8-2.9-8.5V12.5h10.2z'/%3E%3C/svg%3E" alt="Phantom">
|
|
191
|
+
<span>Phantom</span>
|
|
192
|
+
</button>
|
|
193
|
+
|
|
194
|
+
<button class="wallet-btn" onclick="connectSolflare()">
|
|
195
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'%3E%3Cdefs%3E%3ClinearGradient id='a' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23fc1ec1'/%3E%3Cstop offset='100%25' stop-color='%23feb726'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath fill='url(%23a)' d='M20.3 97.7c2.6-2.6 6.9-2.6 9.5 0l39 39c2.6 2.6 6.9 2.6 9.5 0l29.4-29.4c2.6-2.6 6.9-2.6 9.5 0L137 127h-20l-19.2-19.2-29.4 29.4c-2.6 2.6-6.9 2.6-9.5 0l-39-39zm87.4-67.4c-2.6 2.6-6.9 2.6-9.5 0l-39-39c-2.6-2.6-6.9-2.6-9.5 0L20.3 20.7c-2.6 2.6-6.9 2.6-9.5 0L-9 1h20l19.2 19.2 29.4-29.4c2.6-2.6 6.9-2.6 9.5 0l39 39z'/%3E%3C/svg%3E" alt="Solflare">
|
|
196
|
+
<span>Solflare</span>
|
|
197
|
+
</button>
|
|
198
|
+
|
|
199
|
+
<button class="wallet-btn" onclick="connectBackpack()">
|
|
200
|
+
<span>š</span>
|
|
201
|
+
<span>Backpack</span>
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div id="connected-view" style="display: none;">
|
|
206
|
+
<button class="disconnect-btn" onclick="disconnect()">Disconnect Wallet</button>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div class="info">
|
|
210
|
+
<strong>ā¹ļø How it works:</strong><br>
|
|
211
|
+
1. Connect your Solana wallet using a browser extension<br>
|
|
212
|
+
2. The CLI will use this wallet for staking and transactions<br>
|
|
213
|
+
3. You'll approve all transactions in your wallet<br>
|
|
214
|
+
4. No private keys are stored by the CLI
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div id="error" class="error" style="display: none;"></div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<script>
|
|
221
|
+
let ws = null;
|
|
222
|
+
let connected = false;
|
|
223
|
+
|
|
224
|
+
// Connect to WebSocket
|
|
225
|
+
function connectWebSocket() {
|
|
226
|
+
ws = new WebSocket('ws://localhost:3838');
|
|
227
|
+
|
|
228
|
+
ws.onopen = () => {
|
|
229
|
+
console.log('WebSocket connected');
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
ws.onmessage = (event) => {
|
|
233
|
+
const data = JSON.parse(event.data);
|
|
234
|
+
console.log('Received:', data);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
ws.onerror = (error) => {
|
|
238
|
+
console.error('WebSocket error:', error);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
ws.onclose = () => {
|
|
242
|
+
console.log('WebSocket closed, reconnecting...');
|
|
243
|
+
setTimeout(connectWebSocket, 3000);
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Send message to CLI via WebSocket
|
|
248
|
+
function sendToCLI(type, data = {}) {
|
|
249
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
250
|
+
ws.send(JSON.stringify({ type, ...data }));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Update UI
|
|
255
|
+
function updateStatus(icon, text, address = null) {
|
|
256
|
+
const statusDiv = document.getElementById('status');
|
|
257
|
+
statusDiv.innerHTML = `
|
|
258
|
+
<div class="status-icon">${icon}</div>
|
|
259
|
+
<div class="status-text">${text}</div>
|
|
260
|
+
${address ? `<div class="wallet-address">${address}</div>` : ''}
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
if (connected) {
|
|
264
|
+
statusDiv.classList.add('connected');
|
|
265
|
+
document.getElementById('wallet-select').style.display = 'none';
|
|
266
|
+
document.getElementById('connected-view').style.display = 'block';
|
|
267
|
+
} else {
|
|
268
|
+
statusDiv.classList.remove('connected');
|
|
269
|
+
document.getElementById('wallet-select').style.display = 'grid';
|
|
270
|
+
document.getElementById('connected-view').style.display = 'none';
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Show error
|
|
275
|
+
function showError(message) {
|
|
276
|
+
const errorDiv = document.getElementById('error');
|
|
277
|
+
errorDiv.textContent = message;
|
|
278
|
+
errorDiv.style.display = 'block';
|
|
279
|
+
setTimeout(() => {
|
|
280
|
+
errorDiv.style.display = 'none';
|
|
281
|
+
}, 5000);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Phantom Wallet
|
|
285
|
+
async function connectPhantom() {
|
|
286
|
+
try {
|
|
287
|
+
if (!window.solana || !window.solana.isPhantom) {
|
|
288
|
+
window.open('https://phantom.app/', '_blank');
|
|
289
|
+
showError('Phantom wallet not found. Please install the extension.');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
updateStatus('ā³', 'Connecting to Phantom...');
|
|
294
|
+
const resp = await window.solana.connect();
|
|
295
|
+
const publicKey = resp.publicKey.toString();
|
|
296
|
+
|
|
297
|
+
connected = true;
|
|
298
|
+
updateStatus('ā
', 'Connected to Phantom', publicKey);
|
|
299
|
+
sendToCLI('WALLET_CONNECTED', { publicKey, walletType: 'phantom' });
|
|
300
|
+
} catch (error) {
|
|
301
|
+
showError('Failed to connect: ' + error.message);
|
|
302
|
+
updateStatus('š', 'Connect your Solana wallet to continue');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Solflare Wallet
|
|
307
|
+
async function connectSolflare() {
|
|
308
|
+
try {
|
|
309
|
+
if (!window.solflare) {
|
|
310
|
+
window.open('https://solflare.com/', '_blank');
|
|
311
|
+
showError('Solflare wallet not found. Please install the extension.');
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
updateStatus('ā³', 'Connecting to Solflare...');
|
|
316
|
+
await window.solflare.connect();
|
|
317
|
+
const publicKey = window.solflare.publicKey.toString();
|
|
318
|
+
|
|
319
|
+
connected = true;
|
|
320
|
+
updateStatus('ā
', 'Connected to Solflare', publicKey);
|
|
321
|
+
sendToCLI('WALLET_CONNECTED', { publicKey, walletType: 'solflare' });
|
|
322
|
+
} catch (error) {
|
|
323
|
+
showError('Failed to connect: ' + error.message);
|
|
324
|
+
updateStatus('š', 'Connect your Solana wallet to continue');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Backpack Wallet
|
|
329
|
+
async function connectBackpack() {
|
|
330
|
+
try {
|
|
331
|
+
if (!window.backpack) {
|
|
332
|
+
window.open('https://backpack.app/', '_blank');
|
|
333
|
+
showError('Backpack wallet not found. Please install the extension.');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
updateStatus('ā³', 'Connecting to Backpack...');
|
|
338
|
+
const resp = await window.backpack.connect();
|
|
339
|
+
const publicKey = resp.publicKey.toString();
|
|
340
|
+
|
|
341
|
+
connected = true;
|
|
342
|
+
updateStatus('ā
', 'Connected to Backpack', publicKey);
|
|
343
|
+
sendToCLI('WALLET_CONNECTED', { publicKey, walletType: 'backpack' });
|
|
344
|
+
} catch (error) {
|
|
345
|
+
showError('Failed to connect: ' + error.message);
|
|
346
|
+
updateStatus('š', 'Connect your Solana wallet to continue');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Disconnect
|
|
351
|
+
async function disconnect() {
|
|
352
|
+
try {
|
|
353
|
+
if (window.solana && window.solana.isPhantom) {
|
|
354
|
+
await window.solana.disconnect();
|
|
355
|
+
} else if (window.solflare) {
|
|
356
|
+
await window.solflare.disconnect();
|
|
357
|
+
} else if (window.backpack) {
|
|
358
|
+
await window.backpack.disconnect();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
connected = false;
|
|
362
|
+
updateStatus('š', 'Connect your Solana wallet to continue');
|
|
363
|
+
sendToCLI('WALLET_DISCONNECTED');
|
|
364
|
+
} catch (error) {
|
|
365
|
+
showError('Failed to disconnect: ' + error.message);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Initialize
|
|
370
|
+
connectWebSocket();
|
|
371
|
+
</script>
|
|
372
|
+
</body>
|
|
373
|
+
</html>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Wallet Integration
|
|
3
|
+
*
|
|
4
|
+
* Handles staking and transactions using browser wallet extensions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Connection,
|
|
9
|
+
PublicKey,
|
|
10
|
+
Transaction,
|
|
11
|
+
SystemProgram,
|
|
12
|
+
LAMPORTS_PER_SOL
|
|
13
|
+
} from '@solana/web3.js';
|
|
14
|
+
import dotenv from 'dotenv';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { dirname } from 'path';
|
|
18
|
+
|
|
19
|
+
// Get the directory of this module
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// Load .env from project root
|
|
24
|
+
dotenv.config({ path: path.join(__dirname, '..', '.env') });
|
|
25
|
+
|
|
26
|
+
// Solana connection
|
|
27
|
+
const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com';
|
|
28
|
+
const connection = new Connection(RPC_URL, 'confirmed');
|
|
29
|
+
|
|
30
|
+
// Store connected wallet info
|
|
31
|
+
let connectedWalletAddress = null;
|
|
32
|
+
let walletServer = null;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set connected wallet address
|
|
36
|
+
*/
|
|
37
|
+
export function setConnectedWallet(address) {
|
|
38
|
+
connectedWalletAddress = address;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get connected wallet address
|
|
43
|
+
*/
|
|
44
|
+
export function getConnectedWallet() {
|
|
45
|
+
return connectedWalletAddress;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set wallet server reference
|
|
50
|
+
*/
|
|
51
|
+
export function setWalletServer(server) {
|
|
52
|
+
walletServer = server;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get Solana connection
|
|
57
|
+
*/
|
|
58
|
+
export function getConnection() {
|
|
59
|
+
return connection;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get wallet balance
|
|
64
|
+
*/
|
|
65
|
+
export async function getBalance() {
|
|
66
|
+
if (!connectedWalletAddress) {
|
|
67
|
+
throw new Error('No wallet connected');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const publicKey = new PublicKey(connectedWalletAddress);
|
|
72
|
+
const balance = await connection.getBalance(publicKey);
|
|
73
|
+
return balance / LAMPORTS_PER_SOL;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new Error(`Failed to get balance: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if wallet has sufficient balance
|
|
81
|
+
*/
|
|
82
|
+
export async function checkBalance(requiredAmount) {
|
|
83
|
+
const balance = await getBalance();
|
|
84
|
+
|
|
85
|
+
if (balance < requiredAmount) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Insufficient balance: ${balance.toFixed(4)} SOL. Need at least ${requiredAmount} SOL for stake + fees.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create stake transaction (to be signed in browser)
|
|
96
|
+
*/
|
|
97
|
+
export async function createStakeTransaction(amount) {
|
|
98
|
+
if (!connectedWalletAddress) {
|
|
99
|
+
throw new Error('No wallet connected');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const fromPubkey = new PublicKey(connectedWalletAddress);
|
|
104
|
+
const escrowPubkey = getEscrowAddress();
|
|
105
|
+
|
|
106
|
+
// Create transfer transaction
|
|
107
|
+
const transaction = new Transaction().add(
|
|
108
|
+
SystemProgram.transfer({
|
|
109
|
+
fromPubkey,
|
|
110
|
+
toPubkey: escrowPubkey,
|
|
111
|
+
lamports: Math.floor(amount * LAMPORTS_PER_SOL)
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Get recent blockhash
|
|
116
|
+
const { blockhash } = await connection.getLatestBlockhash();
|
|
117
|
+
transaction.recentBlockhash = blockhash;
|
|
118
|
+
transaction.feePayer = fromPubkey;
|
|
119
|
+
|
|
120
|
+
// Serialize transaction for browser signing
|
|
121
|
+
const serialized = transaction.serialize({
|
|
122
|
+
requireAllSignatures: false,
|
|
123
|
+
verifySignatures: false
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
transaction: serialized.toString('base64'),
|
|
128
|
+
amount,
|
|
129
|
+
to: escrowPubkey.toBase58()
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
throw new Error(`Failed to create transaction: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Submit signed transaction
|
|
138
|
+
*/
|
|
139
|
+
export async function submitTransaction(signedTxBase64) {
|
|
140
|
+
try {
|
|
141
|
+
const txBuffer = Buffer.from(signedTxBase64, 'base64');
|
|
142
|
+
const signature = await connection.sendRawTransaction(txBuffer, {
|
|
143
|
+
skipPreflight: false,
|
|
144
|
+
preflightCommitment: 'confirmed'
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await connection.confirmTransaction(signature, 'confirmed');
|
|
148
|
+
return signature;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new Error(`Transaction failed: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get escrow address from env or use default
|
|
156
|
+
*/
|
|
157
|
+
function getEscrowAddress() {
|
|
158
|
+
const escrowAddress = process.env.ORACLE_ESCROW_ADDRESS;
|
|
159
|
+
|
|
160
|
+
if (!escrowAddress || escrowAddress === 'YourDevWalletPublicKeyHere') {
|
|
161
|
+
// Use a valid devnet address for demo
|
|
162
|
+
return new PublicKey('4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
return new PublicKey(escrowAddress);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw new Error(`Invalid ORACLE_ESCROW_ADDRESS in .env: ${error.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Mock stake function (simulates successful stake for demo)
|
|
174
|
+
* In production, this would use the browser wallet to sign
|
|
175
|
+
*/
|
|
176
|
+
export async function mockStake(amount) {
|
|
177
|
+
// Check balance first
|
|
178
|
+
await checkBalance(amount + 0.001); // amount + fees
|
|
179
|
+
|
|
180
|
+
// Generate mock transaction signature
|
|
181
|
+
const mockSignature = 'BROWSER_TX_' + Date.now() + Math.random().toString(36).substring(7);
|
|
182
|
+
|
|
183
|
+
console.log(`Mock stake of ${amount} SOL created`);
|
|
184
|
+
console.log(`Transaction will be signed in browser...`);
|
|
185
|
+
|
|
186
|
+
return mockSignature;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Refund stake (mock for demo)
|
|
191
|
+
*/
|
|
192
|
+
export async function refundStake(amount) {
|
|
193
|
+
console.log(`Refund of ${amount} SOL would be processed from escrow`);
|
|
194
|
+
return 'MOCK_REFUND_TX_' + Date.now();
|
|
195
|
+
}
|
package/src/energy-data.js
CHANGED
|
@@ -6,8 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
import fetch from 'node-fetch';
|
|
8
8
|
import dotenv from 'dotenv';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { dirname } from 'path';
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
// Get the directory of this module
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
|
|
17
|
+
// Load .env from project root (one level up from src/)
|
|
18
|
+
dotenv.config({ path: path.join(__dirname, '..', '.env') });
|
|
11
19
|
|
|
12
20
|
const EIA_API_KEY = process.env.EIA_API_KEY;
|
|
13
21
|
const EIA_BASE_URL = 'https://api.eia.gov/v2';
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Connection Server
|
|
3
|
+
*
|
|
4
|
+
* Starts a local web server for browser-based wallet connection
|
|
5
|
+
* Uses Phantom/Solflare/etc wallet extensions for signing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import express from 'express';
|
|
9
|
+
import { WebSocketServer } from 'ws';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { dirname } from 'path';
|
|
13
|
+
import open from 'open';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
const PORT = 3838;
|
|
21
|
+
|
|
22
|
+
// Store connected wallet info
|
|
23
|
+
let connectedWallet = null;
|
|
24
|
+
let walletResolve = null;
|
|
25
|
+
let txResolve = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Start wallet connection server
|
|
29
|
+
*/
|
|
30
|
+
export async function startWalletServer() {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const server = app.listen(PORT, () => {
|
|
33
|
+
console.log(chalk.blue(`š Wallet server started on http://localhost:${PORT}`));
|
|
34
|
+
resolve(server);
|
|
35
|
+
}).on('error', (err) => {
|
|
36
|
+
if (err.code === 'EADDRINUSE') {
|
|
37
|
+
console.log(chalk.yellow(`ā ļø Port ${PORT} is already in use`));
|
|
38
|
+
resolve(null);
|
|
39
|
+
} else {
|
|
40
|
+
reject(err);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// WebSocket server for real-time communication
|
|
45
|
+
const wss = new WebSocketServer({ server });
|
|
46
|
+
|
|
47
|
+
wss.on('connection', (ws) => {
|
|
48
|
+
console.log(chalk.gray('WebSocket client connected'));
|
|
49
|
+
|
|
50
|
+
ws.on('message', (message) => {
|
|
51
|
+
try {
|
|
52
|
+
const data = JSON.parse(message);
|
|
53
|
+
handleWalletMessage(data, ws);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('WebSocket message error:', error);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Serve static files
|
|
61
|
+
app.use(express.static(path.join(__dirname, '..', 'public')));
|
|
62
|
+
app.use(express.json());
|
|
63
|
+
|
|
64
|
+
// Health check endpoint
|
|
65
|
+
app.get('/api/health', (req, res) => {
|
|
66
|
+
res.json({ status: 'ok', connected: !!connectedWallet });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Get connected wallet
|
|
70
|
+
app.get('/api/wallet', (req, res) => {
|
|
71
|
+
if (connectedWallet) {
|
|
72
|
+
res.json({ connected: true, publicKey: connectedWallet });
|
|
73
|
+
} else {
|
|
74
|
+
res.json({ connected: false });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handle wallet messages from browser
|
|
82
|
+
*/
|
|
83
|
+
function handleWalletMessage(data, ws) {
|
|
84
|
+
switch (data.type) {
|
|
85
|
+
case 'WALLET_CONNECTED':
|
|
86
|
+
connectedWallet = data.publicKey;
|
|
87
|
+
console.log(chalk.green(`ā Wallet connected: ${data.publicKey}`));
|
|
88
|
+
if (walletResolve) {
|
|
89
|
+
walletResolve(data.publicKey);
|
|
90
|
+
walletResolve = null;
|
|
91
|
+
}
|
|
92
|
+
ws.send(JSON.stringify({ type: 'ACK', message: 'Wallet connected successfully' }));
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case 'WALLET_DISCONNECTED':
|
|
96
|
+
connectedWallet = null;
|
|
97
|
+
console.log(chalk.yellow('Wallet disconnected'));
|
|
98
|
+
ws.send(JSON.stringify({ type: 'ACK', message: 'Wallet disconnected' }));
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case 'TX_SIGNED':
|
|
102
|
+
console.log(chalk.green('ā Transaction signed'));
|
|
103
|
+
if (txResolve) {
|
|
104
|
+
txResolve(data.signature);
|
|
105
|
+
txResolve = null;
|
|
106
|
+
}
|
|
107
|
+
ws.send(JSON.stringify({ type: 'ACK', message: 'Transaction received' }));
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case 'TX_REJECTED':
|
|
111
|
+
console.log(chalk.red('ā Transaction rejected by user'));
|
|
112
|
+
if (txResolve) {
|
|
113
|
+
txResolve(null);
|
|
114
|
+
txResolve = null;
|
|
115
|
+
}
|
|
116
|
+
ws.send(JSON.stringify({ type: 'ACK', message: 'Transaction cancelled' }));
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case 'ERROR':
|
|
120
|
+
console.log(chalk.red(`Error: ${data.message}`));
|
|
121
|
+
ws.send(JSON.stringify({ type: 'ACK', message: 'Error received' }));
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
default:
|
|
125
|
+
console.log(chalk.gray(`Unknown message type: ${data.type}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Wait for wallet connection
|
|
131
|
+
*/
|
|
132
|
+
export async function waitForWalletConnection() {
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
if (connectedWallet) {
|
|
135
|
+
resolve(connectedWallet);
|
|
136
|
+
} else {
|
|
137
|
+
walletResolve = resolve;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Request transaction signature from browser wallet
|
|
144
|
+
*/
|
|
145
|
+
export async function requestTransactionSignature(transaction) {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
txResolve = resolve;
|
|
148
|
+
// Transaction will be sent via WebSocket
|
|
149
|
+
// Browser will prompt user to sign
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Open browser for wallet connection
|
|
155
|
+
*/
|
|
156
|
+
export async function openWalletConnection() {
|
|
157
|
+
const url = `http://localhost:${PORT}`;
|
|
158
|
+
console.log(chalk.blue(`\nš Opening browser for wallet connection...`));
|
|
159
|
+
console.log(chalk.gray(`URL: ${url}`));
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await open(url);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.log(chalk.yellow(`\nā ļø Could not auto-open browser`));
|
|
165
|
+
console.log(chalk.blue(`Please open manually: ${url}`));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get connected wallet public key
|
|
171
|
+
*/
|
|
172
|
+
export function getConnectedWallet() {
|
|
173
|
+
return connectedWallet;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if wallet is connected
|
|
178
|
+
*/
|
|
179
|
+
export function isWalletConnected() {
|
|
180
|
+
return !!connectedWallet;
|
|
181
|
+
}
|