@darksol/terminal 0.4.0 → 0.4.1-beta.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.4.0",
3
+ "version": "0.4.1-beta.1",
4
4
  "description": "DARKSOL Terminal — unified CLI for all DARKSOL services. Market intel, trading, oracle, casino, and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,7 +43,8 @@
43
43
  "open": "^11.0.0",
44
44
  "ora": "^8.1.0",
45
45
  "terminal-link": "^3.0.0",
46
- "update-notifier": "^7.3.1"
46
+ "update-notifier": "^7.3.1",
47
+ "ws": "^8.19.0"
47
48
  },
48
49
  "engines": {
49
50
  "node": ">=18.0.0"
package/src/cli.js CHANGED
@@ -9,6 +9,7 @@ import { showHistory } from './wallet/history.js';
9
9
  import { showGas } from './services/gas.js';
10
10
  import { watchPrice, checkPrices } from './services/watch.js';
11
11
  import { mailSetup, mailCreate, mailInboxes, mailSend, mailList, mailRead, mailReply, mailForward, mailThreads, mailDelete, mailUse, mailStats, mailStatus } from './services/mail.js';
12
+ import { startWebShell } from './web/server.js';
12
13
  import { executeSwap } from './trading/swap.js';
13
14
  import { snipeToken, watchSnipe } from './trading/snipe.js';
14
15
  import { createDCA, listDCA, cancelDCA, runDCA } from './trading/dca.js';
@@ -32,7 +33,7 @@ export function cli(argv) {
32
33
  program
33
34
  .name('darksol')
34
35
  .description(theme.gold('DARKSOL Terminal') + theme.dim(' — Ghost in the machine with teeth 🌑'))
35
- .version('0.1.0')
36
+ .version('0.4.1')
36
37
  ;
37
38
 
38
39
  // ═══════════════════════════════════════
@@ -398,6 +399,16 @@ export function cli(argv) {
398
399
  .description('Delete an inbox')
399
400
  .action((id) => mailDelete(id));
400
401
 
402
+ // ═══════════════════════════════════════
403
+ // WEB SHELL
404
+ // ═══════════════════════════════════════
405
+ program
406
+ .command('serve')
407
+ .description('🌐 Launch web terminal in browser')
408
+ .option('-p, --port <port>', 'Server port', '18791')
409
+ .option('--no-open', 'Don\'t auto-open browser')
410
+ .action((opts) => startWebShell(opts));
411
+
401
412
  // ═══════════════════════════════════════
402
413
  // PORTFOLIO SHORTCUT
403
414
  // ═══════════════════════════════════════
@@ -932,6 +943,7 @@ function showCommandList() {
932
943
  ['mail', 'AgentMail — email for your agent'],
933
944
  ['facilitator', 'x402 payment facilitator'],
934
945
  ['skills', 'Agent skill directory'],
946
+ ['serve', 'Launch web terminal in browser'],
935
947
  ['setup', 'Re-run setup wizard'],
936
948
  ['config', 'Terminal configuration'],
937
949
  ];
@@ -56,6 +56,18 @@ const SWAP_ROUTER_ABI = [
56
56
  'function multicall(uint256 deadline, bytes[] data) external payable returns (bytes[])',
57
57
  ];
58
58
 
59
+ // Uniswap V3 Quoter V2 ABI (for getting expected output)
60
+ const QUOTER_ABI = [
61
+ 'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
62
+ ];
63
+
64
+ // Quoter V2 addresses per chain
65
+ const QUOTERS = {
66
+ base: '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a',
67
+ ethereum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
68
+ arbitrum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
69
+ };
70
+
59
71
  // Resolve token symbol to address
60
72
  export function resolveToken(symbol, chain) {
61
73
  const upper = symbol.toUpperCase();
@@ -177,7 +189,7 @@ export async function executeSwap(opts = {}) {
177
189
  return;
178
190
  }
179
191
 
180
- const swapSpin = spinner('Executing swap...').start();
192
+ let swapSpin = spinner('Executing swap...').start();
181
193
 
182
194
  // Approve if needed (non-native)
183
195
  if (!isNativeIn) {
@@ -191,15 +203,54 @@ export async function executeSwap(opts = {}) {
191
203
  }
192
204
 
193
205
  // Execute swap
194
- swapSpin.text = 'Sending swap transaction...';
206
+ swapSpin.text = 'Getting quote for slippage protection...';
195
207
  const swapRouter = new ethers.Contract(router, SWAP_ROUTER_ABI, signer);
208
+ const actualTokenOut = tokenOutAddr === ethers.ZeroAddress ? TOKENS[chain]?.WETH : tokenOutAddr;
196
209
 
197
210
  const deadline = Math.floor(Date.now() / 1000) + 300; // 5 min
198
- const amountOutMin = 0; // TODO: get quote for proper slippage protection
199
211
 
212
+ // Get quote from Quoter V2 for proper slippage protection
213
+ let amountOutMin = 0n;
214
+ const quoterAddr = QUOTERS[chain];
215
+ if (quoterAddr) {
216
+ try {
217
+ const quoter = new ethers.Contract(quoterAddr, QUOTER_ABI, provider);
218
+ const quoteResult = await quoter.quoteExactInputSingle.staticCall({
219
+ tokenIn: actualTokenIn,
220
+ tokenOut: actualTokenOut,
221
+ amountIn,
222
+ fee: 3000,
223
+ sqrtPriceLimitX96: 0,
224
+ });
225
+ // staticCall returns [amountOut, sqrtPriceX96After, initializedTicksCrossed, gasEstimate]
226
+ const expectedOut = quoteResult[0];
227
+ // Apply slippage tolerance: minOut = expectedOut * (100 - slippage) / 100
228
+ amountOutMin = (expectedOut * BigInt(Math.floor((100 - maxSlippage) * 100))) / 10000n;
229
+ swapSpin.text = `Quote: ~${ethers.formatUnits(expectedOut, tokenOutInfo.decimals)} ${tokenOutInfo.symbol} (min: ${ethers.formatUnits(amountOutMin, tokenOutInfo.decimals)})`;
230
+ } catch (quoteErr) {
231
+ // If quote fails, warn but allow user to proceed with zero protection
232
+ swapSpin.warn('Could not get quote — no slippage protection');
233
+ warn(`Quote failed: ${quoteErr.message}`);
234
+ const { proceedAnyway } = await inquirer.prompt([{
235
+ type: 'confirm',
236
+ name: 'proceedAnyway',
237
+ message: theme.accent('Proceed WITHOUT slippage protection? (risky)'),
238
+ default: false,
239
+ }]);
240
+ if (!proceedAnyway) {
241
+ warn('Swap cancelled — no slippage protection available');
242
+ return;
243
+ }
244
+ swapSpin = spinner('Sending swap transaction...').start();
245
+ }
246
+ } else {
247
+ warn(`No Quoter available for ${chain} — swap will have no slippage protection`);
248
+ }
249
+
250
+ swapSpin.text = 'Sending swap transaction...';
200
251
  const swapParams = {
201
252
  tokenIn: actualTokenIn,
202
- tokenOut: tokenOutAddr === ethers.ZeroAddress ? TOKENS[chain]?.WETH : tokenOutAddr,
253
+ tokenOut: actualTokenOut,
203
254
  fee: 3000, // 0.3% fee tier
204
255
  recipient: address,
205
256
  deadline,
@@ -222,6 +273,7 @@ export async function executeSwap(opts = {}) {
222
273
  ['TX Hash', receipt.hash],
223
274
  ['Block', receipt.blockNumber.toString()],
224
275
  ['Gas Used', receipt.gasUsed.toString()],
276
+ ['Min Output', amountOutMin > 0n ? `${ethers.formatUnits(amountOutMin, tokenOutInfo.decimals)} ${tokenOutInfo.symbol}` : theme.error('None (unprotected)')],
225
277
  ['Status', receipt.status === 1 ? theme.success('Success') : theme.error('Failed')],
226
278
  ]);
227
279
  console.log('');
package/src/ui/banner.js CHANGED
@@ -26,7 +26,7 @@ export function showBanner(opts = {}) {
26
26
  );
27
27
  console.log(
28
28
  theme.dim(' ║ ') +
29
- theme.subtle(' v0.4.0') +
29
+ theme.subtle(' v0.4.1-beta.1') +
30
30
  theme.dim(' ') +
31
31
  theme.gold('🌑') +
32
32
  theme.dim(' ║')
@@ -44,7 +44,7 @@ export function showBanner(opts = {}) {
44
44
 
45
45
  export function showMiniBanner() {
46
46
  console.log('');
47
- console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.4.0'));
47
+ console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.4.1-beta.1'));
48
48
  console.log(theme.dim(' ─────────────────────────────'));
49
49
  console.log('');
50
50
  }
@@ -72,3 +72,4 @@ export function showDivider() {
72
72
 
73
73
 
74
74
 
75
+