@darksol/terminal 0.1.0 → 0.1.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/README.md +99 -0
- package/package.json +12 -3
- package/src/cli.js +60 -0
- package/src/ui/banner.js +3 -2
- package/src/utils/helpers.js +677 -0
package/README.md
CHANGED
|
@@ -184,6 +184,105 @@ JSON output mode for programmatic use:
|
|
|
184
184
|
darksol config set output json
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
+
## Helper Functions
|
|
188
|
+
|
|
189
|
+
When writing custom execution scripts, you have access to powerful helper utilities:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
import {
|
|
193
|
+
// Providers & Chain
|
|
194
|
+
getProvider, // Get ethers provider for any chain
|
|
195
|
+
CHAIN_IDS, // { base: 8453, ethereum: 1, ... }
|
|
196
|
+
EXPLORERS, // Block explorer URLs per chain
|
|
197
|
+
txUrl, addressUrl, // Generate explorer links
|
|
198
|
+
|
|
199
|
+
// Tokens
|
|
200
|
+
getERC20, // Get ERC20 contract instance
|
|
201
|
+
getFullTokenInfo, // Name, symbol, decimals, totalSupply
|
|
202
|
+
getTokenBalance, // Formatted balance for any token
|
|
203
|
+
ensureApproval, // Check & approve token spending
|
|
204
|
+
TOKENS, // All known token addresses per chain
|
|
205
|
+
getUSDC, getWETH, // Quick chain-specific lookups
|
|
206
|
+
|
|
207
|
+
// Gas
|
|
208
|
+
estimateGasCost, // Estimate gas in ETH
|
|
209
|
+
getBoostedGas, // Priority gas settings for snipes
|
|
210
|
+
|
|
211
|
+
// Formatting
|
|
212
|
+
formatCompact, // 1234567 → "1.23M"
|
|
213
|
+
formatUSD, // Format as $1,234.56
|
|
214
|
+
formatETH, // Format wei to ETH string
|
|
215
|
+
formatTokenAmount, // Format with symbol
|
|
216
|
+
shortAddress, // 0x1234...5678
|
|
217
|
+
formatDuration, // Seconds → "2h 30m"
|
|
218
|
+
|
|
219
|
+
// Validation
|
|
220
|
+
isValidAddress, // Check Ethereum address
|
|
221
|
+
isValidPrivateKey, // Check private key format
|
|
222
|
+
isValidAmount, // Check positive number
|
|
223
|
+
parseTokenAmount, // String → bigint with decimals
|
|
224
|
+
|
|
225
|
+
// Async
|
|
226
|
+
sleep, // await sleep(1000)
|
|
227
|
+
retry, // Retry with exponential backoff
|
|
228
|
+
waitForTx, // Wait for tx with timeout
|
|
229
|
+
|
|
230
|
+
// Price
|
|
231
|
+
quickPrice, // DexScreener price lookup
|
|
232
|
+
hasLiquidity, // Check minimum liquidity
|
|
233
|
+
} from './utils/helpers.js';
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Example: Custom Script Using Helpers
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
module.exports = async function({ signer, provider, ethers, config, params }) {
|
|
240
|
+
// Import helpers (available in script context)
|
|
241
|
+
const helpers = await import('@darksol/terminal/src/utils/helpers.js');
|
|
242
|
+
|
|
243
|
+
// Check if token has enough liquidity
|
|
244
|
+
const liquid = await helpers.hasLiquidity(params.token, 5000);
|
|
245
|
+
if (!liquid) throw new Error('Insufficient liquidity');
|
|
246
|
+
|
|
247
|
+
// Get price
|
|
248
|
+
const price = await helpers.quickPrice(params.token);
|
|
249
|
+
console.log(`Price: ${helpers.formatUSD(price.price)}`);
|
|
250
|
+
|
|
251
|
+
// Get boosted gas for priority
|
|
252
|
+
const gas = await helpers.getBoostedGas(provider, 1.5);
|
|
253
|
+
|
|
254
|
+
// Execute trade with retry
|
|
255
|
+
const result = await helpers.retry(async () => {
|
|
256
|
+
const tx = await signer.sendTransaction({ ...txParams, ...gas });
|
|
257
|
+
return helpers.waitForTx(tx, 60000);
|
|
258
|
+
}, 3, 2000);
|
|
259
|
+
|
|
260
|
+
return { txHash: result.hash, price: price.price };
|
|
261
|
+
};
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Tips & Reference
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Trading tips (slippage, MEV protection, etc.)
|
|
268
|
+
darksol tips --trading
|
|
269
|
+
|
|
270
|
+
# Script writing tips
|
|
271
|
+
darksol tips --scripts
|
|
272
|
+
|
|
273
|
+
# Both
|
|
274
|
+
darksol tips
|
|
275
|
+
|
|
276
|
+
# Network reference (chains, IDs, explorers, USDC addresses)
|
|
277
|
+
darksol networks
|
|
278
|
+
|
|
279
|
+
# Getting started guide
|
|
280
|
+
darksol quickstart
|
|
281
|
+
|
|
282
|
+
# Look up any address (auto-detects token vs wallet)
|
|
283
|
+
darksol lookup 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
|
|
284
|
+
```
|
|
285
|
+
|
|
187
286
|
## Development
|
|
188
287
|
|
|
189
288
|
```bash
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darksol/terminal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.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": {
|
|
7
|
-
"darksol": "
|
|
7
|
+
"darksol": "bin/darksol.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./src/cli.js",
|
|
10
10
|
"scripts": {
|
|
@@ -12,7 +12,16 @@
|
|
|
12
12
|
"dev": "node bin/darksol.js dashboard",
|
|
13
13
|
"test": "node --test tests/*.test.js"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"darksol",
|
|
17
|
+
"crypto",
|
|
18
|
+
"trading",
|
|
19
|
+
"cli",
|
|
20
|
+
"x402",
|
|
21
|
+
"base",
|
|
22
|
+
"ethereum",
|
|
23
|
+
"defi"
|
|
24
|
+
],
|
|
16
25
|
"author": "DARKSOL <chris00claw@gmail.com>",
|
|
17
26
|
"license": "MIT",
|
|
18
27
|
"dependencies": {
|
package/src/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { cardsCatalog, cardsOrder, cardsStatus } from './services/cards.js';
|
|
|
14
14
|
import { facilitatorHealth, facilitatorVerify, facilitatorSettle } from './services/facilitator.js';
|
|
15
15
|
import { buildersLeaderboard, buildersLookup, buildersFeed } from './services/builders.js';
|
|
16
16
|
import { createScript, listScripts, runScript, showScript, editScript, deleteScript, cloneScript, listTemplates } from './scripts/engine.js';
|
|
17
|
+
import { showTradingTips, showScriptTips, showNetworkReference, showQuickStart, showWalletSummary, showTokenInfo, showTxResult } from './utils/helpers.js';
|
|
17
18
|
|
|
18
19
|
export function cli(argv) {
|
|
19
20
|
const program = new Command();
|
|
@@ -288,6 +289,61 @@ export function cli(argv) {
|
|
|
288
289
|
.description('Settle payment on-chain')
|
|
289
290
|
.action((payment) => facilitatorSettle(payment));
|
|
290
291
|
|
|
292
|
+
// ═══════════════════════════════════════
|
|
293
|
+
// TIPS & REFERENCE COMMANDS
|
|
294
|
+
// ═══════════════════════════════════════
|
|
295
|
+
program
|
|
296
|
+
.command('tips')
|
|
297
|
+
.description('Show trading and script writing tips')
|
|
298
|
+
.option('-t, --trading', 'Trading tips only')
|
|
299
|
+
.option('-s, --scripts', 'Script writing tips only')
|
|
300
|
+
.action((opts) => {
|
|
301
|
+
showMiniBanner();
|
|
302
|
+
if (opts.scripts) {
|
|
303
|
+
showScriptTips();
|
|
304
|
+
} else if (opts.trading) {
|
|
305
|
+
showTradingTips();
|
|
306
|
+
} else {
|
|
307
|
+
showTradingTips();
|
|
308
|
+
showScriptTips();
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
program
|
|
313
|
+
.command('networks')
|
|
314
|
+
.description('Show supported networks and chain info')
|
|
315
|
+
.action(() => {
|
|
316
|
+
showMiniBanner();
|
|
317
|
+
showNetworkReference();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
program
|
|
321
|
+
.command('quickstart')
|
|
322
|
+
.description('Show getting started guide')
|
|
323
|
+
.action(() => {
|
|
324
|
+
showMiniBanner();
|
|
325
|
+
showQuickStart();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
program
|
|
329
|
+
.command('lookup <address>')
|
|
330
|
+
.description('Look up a token or wallet address on-chain')
|
|
331
|
+
.option('-c, --chain <chain>', 'Chain to query')
|
|
332
|
+
.action(async (address, opts) => {
|
|
333
|
+
showMiniBanner();
|
|
334
|
+
if (address.length === 42 && address.startsWith('0x')) {
|
|
335
|
+
// Could be token or wallet — try token first
|
|
336
|
+
try {
|
|
337
|
+
await showTokenInfo(address, opts.chain);
|
|
338
|
+
} catch {
|
|
339
|
+
await showWalletSummary(address, opts.chain);
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
const { error } = await import('./ui/components.js');
|
|
343
|
+
error('Provide a valid 0x address');
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
291
347
|
// ═══════════════════════════════════════
|
|
292
348
|
// SCRIPT COMMANDS
|
|
293
349
|
// ═══════════════════════════════════════
|
|
@@ -418,6 +474,10 @@ export function cli(argv) {
|
|
|
418
474
|
['builders', 'ERC-8021 builder index'],
|
|
419
475
|
['facilitator', 'x402 payment facilitator'],
|
|
420
476
|
['config', 'Terminal configuration'],
|
|
477
|
+
['tips', 'Trading & scripting tips'],
|
|
478
|
+
['networks', 'Chain reference & explorers'],
|
|
479
|
+
['quickstart', 'Getting started guide'],
|
|
480
|
+
['lookup', 'Look up any address on-chain'],
|
|
421
481
|
];
|
|
422
482
|
|
|
423
483
|
commands.forEach(([cmd, desc]) => {
|
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.1.
|
|
29
|
+
theme.subtle(' v0.1.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.1.
|
|
47
|
+
console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.1.1'));
|
|
48
48
|
console.log(theme.dim(' ─────────────────────────────'));
|
|
49
49
|
console.log('');
|
|
50
50
|
}
|
|
@@ -58,3 +58,4 @@ export function showSection(title) {
|
|
|
58
58
|
export function showDivider() {
|
|
59
59
|
console.log(theme.dim(' ' + '─'.repeat(50)));
|
|
60
60
|
}
|
|
61
|
+
|
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import { getConfig, getRPC } from '../config/store.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { spinner, kvDisplay, success, error, warn, info, table } from '../ui/components.js';
|
|
5
|
+
import { showSection } from '../ui/banner.js';
|
|
6
|
+
|
|
7
|
+
// ──────────────────────────────────────────────────
|
|
8
|
+
// PROVIDER & CHAIN HELPERS
|
|
9
|
+
// ──────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get an ethers provider for a given chain (or the active chain)
|
|
13
|
+
* @param {string} [chain] - Chain name (base, ethereum, polygon, arbitrum, optimism)
|
|
14
|
+
* @returns {ethers.JsonRpcProvider}
|
|
15
|
+
*/
|
|
16
|
+
export function getProvider(chain) {
|
|
17
|
+
const rpc = getRPC(chain || getConfig('chain'));
|
|
18
|
+
return new ethers.JsonRpcProvider(rpc);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the chain ID for a chain name
|
|
23
|
+
*/
|
|
24
|
+
export const CHAIN_IDS = {
|
|
25
|
+
ethereum: 1,
|
|
26
|
+
optimism: 10,
|
|
27
|
+
polygon: 137,
|
|
28
|
+
arbitrum: 42161,
|
|
29
|
+
base: 8453,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get a block explorer URL for a given chain
|
|
34
|
+
*/
|
|
35
|
+
export const EXPLORERS = {
|
|
36
|
+
base: 'https://basescan.org',
|
|
37
|
+
ethereum: 'https://etherscan.io',
|
|
38
|
+
polygon: 'https://polygonscan.com',
|
|
39
|
+
arbitrum: 'https://arbiscan.io',
|
|
40
|
+
optimism: 'https://optimistic.etherscan.io',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the block explorer TX URL
|
|
45
|
+
* @param {string} txHash
|
|
46
|
+
* @param {string} [chain]
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function txUrl(txHash, chain) {
|
|
50
|
+
const explorer = EXPLORERS[chain || getConfig('chain')] || EXPLORERS.base;
|
|
51
|
+
return `${explorer}/tx/${txHash}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the block explorer address URL
|
|
56
|
+
* @param {string} address
|
|
57
|
+
* @param {string} [chain]
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function addressUrl(address, chain) {
|
|
61
|
+
const explorer = EXPLORERS[chain || getConfig('chain')] || EXPLORERS.base;
|
|
62
|
+
return `${explorer}/address/${address}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the block explorer token URL
|
|
67
|
+
* @param {string} tokenAddress
|
|
68
|
+
* @param {string} [chain]
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
export function tokenUrl(tokenAddress, chain) {
|
|
72
|
+
const explorer = EXPLORERS[chain || getConfig('chain')] || EXPLORERS.base;
|
|
73
|
+
return `${explorer}/token/${tokenAddress}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// ──────────────────────────────────────────────────
|
|
78
|
+
// TOKEN HELPERS
|
|
79
|
+
// ──────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
const ERC20_ABI = [
|
|
82
|
+
'function name() view returns (string)',
|
|
83
|
+
'function symbol() view returns (string)',
|
|
84
|
+
'function decimals() view returns (uint8)',
|
|
85
|
+
'function totalSupply() view returns (uint256)',
|
|
86
|
+
'function balanceOf(address) view returns (uint256)',
|
|
87
|
+
'function allowance(address owner, address spender) view returns (uint256)',
|
|
88
|
+
'function approve(address spender, uint256 amount) returns (bool)',
|
|
89
|
+
'function transfer(address to, uint256 amount) returns (bool)',
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get a connected ERC20 contract instance
|
|
94
|
+
* @param {string} address - Token contract address
|
|
95
|
+
* @param {ethers.Signer|ethers.Provider} signerOrProvider
|
|
96
|
+
* @returns {ethers.Contract}
|
|
97
|
+
*/
|
|
98
|
+
export function getERC20(address, signerOrProvider) {
|
|
99
|
+
return new ethers.Contract(address, ERC20_ABI, signerOrProvider);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get full token info: name, symbol, decimals, totalSupply
|
|
104
|
+
* @param {string} address
|
|
105
|
+
* @param {ethers.Provider} provider
|
|
106
|
+
* @returns {Promise<{name: string, symbol: string, decimals: number, totalSupply: bigint, address: string}>}
|
|
107
|
+
*/
|
|
108
|
+
export async function getFullTokenInfo(address, provider) {
|
|
109
|
+
const token = getERC20(address, provider);
|
|
110
|
+
const [name, symbol, decimals, totalSupply] = await Promise.all([
|
|
111
|
+
token.name(),
|
|
112
|
+
token.symbol(),
|
|
113
|
+
token.decimals(),
|
|
114
|
+
token.totalSupply(),
|
|
115
|
+
]);
|
|
116
|
+
return {
|
|
117
|
+
name,
|
|
118
|
+
symbol,
|
|
119
|
+
decimals: Number(decimals),
|
|
120
|
+
totalSupply,
|
|
121
|
+
address,
|
|
122
|
+
formattedSupply: ethers.formatUnits(totalSupply, Number(decimals)),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get token balance for an address
|
|
128
|
+
* @param {string} tokenAddress
|
|
129
|
+
* @param {string} walletAddress
|
|
130
|
+
* @param {ethers.Provider} provider
|
|
131
|
+
* @returns {Promise<{raw: bigint, formatted: string, symbol: string}>}
|
|
132
|
+
*/
|
|
133
|
+
export async function getTokenBalance(tokenAddress, walletAddress, provider) {
|
|
134
|
+
const token = getERC20(tokenAddress, provider);
|
|
135
|
+
const [balance, decimals, symbol] = await Promise.all([
|
|
136
|
+
token.balanceOf(walletAddress),
|
|
137
|
+
token.decimals(),
|
|
138
|
+
token.symbol(),
|
|
139
|
+
]);
|
|
140
|
+
return {
|
|
141
|
+
raw: balance,
|
|
142
|
+
formatted: ethers.formatUnits(balance, Number(decimals)),
|
|
143
|
+
symbol,
|
|
144
|
+
decimals: Number(decimals),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check and approve token spending if needed
|
|
150
|
+
* @param {ethers.Contract} token - ERC20 contract connected to signer
|
|
151
|
+
* @param {string} spender - Address to approve
|
|
152
|
+
* @param {bigint} amount - Amount to approve
|
|
153
|
+
* @param {ethers.Signer} signer
|
|
154
|
+
* @returns {Promise<boolean>} true if approval tx was sent
|
|
155
|
+
*/
|
|
156
|
+
export async function ensureApproval(token, spender, amount, signer) {
|
|
157
|
+
const owner = await signer.getAddress();
|
|
158
|
+
const allowance = await token.allowance(owner, spender);
|
|
159
|
+
if (allowance >= amount) return false;
|
|
160
|
+
|
|
161
|
+
const tx = await token.approve(spender, ethers.MaxUint256);
|
|
162
|
+
await tx.wait();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// ──────────────────────────────────────────────────
|
|
168
|
+
// COMMON TOKEN ADDRESSES
|
|
169
|
+
// ──────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
export const TOKENS = {
|
|
172
|
+
base: {
|
|
173
|
+
WETH: '0x4200000000000000000000000000000000000006',
|
|
174
|
+
USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
175
|
+
USDbC: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
|
|
176
|
+
DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
|
|
177
|
+
AERO: '0x940181a94A35A4569E4529A3CDfB74e38FD98631',
|
|
178
|
+
VIRTUAL: '0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b',
|
|
179
|
+
cbETH: '0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22',
|
|
180
|
+
},
|
|
181
|
+
ethereum: {
|
|
182
|
+
WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
|
183
|
+
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
184
|
+
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
185
|
+
DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
186
|
+
WBTC: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
|
187
|
+
LINK: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
|
|
188
|
+
},
|
|
189
|
+
polygon: {
|
|
190
|
+
WMATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
|
|
191
|
+
USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
|
|
192
|
+
USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
|
193
|
+
},
|
|
194
|
+
arbitrum: {
|
|
195
|
+
WETH: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
|
|
196
|
+
USDC: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
|
197
|
+
ARB: '0x912CE59144191C1204E64559FE8253a0e49E6548',
|
|
198
|
+
GMX: '0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a',
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get USDC address for a chain
|
|
204
|
+
*/
|
|
205
|
+
export function getUSDC(chain) {
|
|
206
|
+
return TOKENS[chain || getConfig('chain')]?.USDC;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get WETH address for a chain
|
|
211
|
+
*/
|
|
212
|
+
export function getWETH(chain) {
|
|
213
|
+
const c = chain || getConfig('chain');
|
|
214
|
+
if (c === 'polygon') return TOKENS.polygon.WMATIC;
|
|
215
|
+
return TOKENS[c]?.WETH;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
// ──────────────────────────────────────────────────
|
|
220
|
+
// GAS HELPERS
|
|
221
|
+
// ──────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Estimate gas cost in ETH
|
|
225
|
+
* @param {ethers.Provider} provider
|
|
226
|
+
* @param {bigint} gasLimit
|
|
227
|
+
* @returns {Promise<{gwei: string, ethCost: string, maxFee: bigint, priorityFee: bigint}>}
|
|
228
|
+
*/
|
|
229
|
+
export async function estimateGasCost(provider, gasLimit = 21000n) {
|
|
230
|
+
const feeData = await provider.getFeeData();
|
|
231
|
+
const maxFee = feeData.maxFeePerGas || feeData.gasPrice || 0n;
|
|
232
|
+
const priorityFee = feeData.maxPriorityFeePerGas || 0n;
|
|
233
|
+
const totalCost = maxFee * gasLimit;
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
gwei: ethers.formatUnits(maxFee, 'gwei'),
|
|
237
|
+
ethCost: ethers.formatEther(totalCost),
|
|
238
|
+
maxFee,
|
|
239
|
+
priorityFee,
|
|
240
|
+
gasLimit,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get boosted gas settings (for snipes and priority txs)
|
|
246
|
+
* @param {ethers.Provider} provider
|
|
247
|
+
* @param {number} multiplier - Gas price multiplier (e.g., 1.5)
|
|
248
|
+
* @returns {Promise<{maxFeePerGas: bigint, maxPriorityFeePerGas: bigint}>}
|
|
249
|
+
*/
|
|
250
|
+
export async function getBoostedGas(provider, multiplier = 1.5) {
|
|
251
|
+
const feeData = await provider.getFeeData();
|
|
252
|
+
const mult = BigInt(Math.floor(multiplier * 100));
|
|
253
|
+
return {
|
|
254
|
+
maxFeePerGas: feeData.maxFeePerGas ? (feeData.maxFeePerGas * mult) / 100n : undefined,
|
|
255
|
+
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ? (feeData.maxPriorityFeePerGas * mult) / 100n : undefined,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
// ──────────────────────────────────────────────────
|
|
261
|
+
// FORMATTING HELPERS
|
|
262
|
+
// ──────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Format a number with commas (e.g., 1234567 → "1,234,567")
|
|
266
|
+
*/
|
|
267
|
+
export function formatNumber(num) {
|
|
268
|
+
return Number(num).toLocaleString('en-US');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Format a large number compactly (e.g., 1234567 → "1.23M")
|
|
273
|
+
*/
|
|
274
|
+
export function formatCompact(num) {
|
|
275
|
+
num = parseFloat(num);
|
|
276
|
+
if (isNaN(num)) return '0';
|
|
277
|
+
if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T';
|
|
278
|
+
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
|
|
279
|
+
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
|
|
280
|
+
if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K';
|
|
281
|
+
return num.toFixed(2);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Format a USD value
|
|
286
|
+
*/
|
|
287
|
+
export function formatUSD(num) {
|
|
288
|
+
const n = parseFloat(num);
|
|
289
|
+
if (isNaN(n)) return '$0.00';
|
|
290
|
+
if (n < 0.01 && n > 0) return '$' + n.toPrecision(4);
|
|
291
|
+
return '$' + n.toFixed(2);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Format ETH amount
|
|
296
|
+
*/
|
|
297
|
+
export function formatETH(wei, decimals = 6) {
|
|
298
|
+
return parseFloat(ethers.formatEther(wei)).toFixed(decimals) + ' ETH';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Format token amount with symbol
|
|
303
|
+
*/
|
|
304
|
+
export function formatTokenAmount(raw, decimals, symbol) {
|
|
305
|
+
return parseFloat(ethers.formatUnits(raw, decimals)).toFixed(6) + ' ' + symbol;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Shorten an address (e.g., 0x1234...5678)
|
|
310
|
+
*/
|
|
311
|
+
export function shortAddress(address, chars = 6) {
|
|
312
|
+
if (!address) return 'N/A';
|
|
313
|
+
return `${address.slice(0, chars)}...${address.slice(-4)}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Format a timestamp to local date/time string
|
|
318
|
+
*/
|
|
319
|
+
export function formatTime(timestamp) {
|
|
320
|
+
return new Date(timestamp).toLocaleString();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Format a duration in seconds to human-readable
|
|
325
|
+
*/
|
|
326
|
+
export function formatDuration(seconds) {
|
|
327
|
+
if (seconds < 60) return `${seconds}s`;
|
|
328
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
329
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
|
|
330
|
+
return `${Math.floor(seconds / 86400)}d ${Math.floor((seconds % 86400) / 3600)}h`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
// ──────────────────────────────────────────────────
|
|
335
|
+
// VALIDATION HELPERS
|
|
336
|
+
// ──────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Validate an Ethereum address
|
|
340
|
+
*/
|
|
341
|
+
export function isValidAddress(address) {
|
|
342
|
+
try {
|
|
343
|
+
return ethers.isAddress(address);
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Validate a private key
|
|
351
|
+
*/
|
|
352
|
+
export function isValidPrivateKey(key) {
|
|
353
|
+
try {
|
|
354
|
+
new ethers.Wallet(key);
|
|
355
|
+
return true;
|
|
356
|
+
} catch {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Validate a numeric amount (positive, non-zero)
|
|
363
|
+
*/
|
|
364
|
+
export function isValidAmount(amount) {
|
|
365
|
+
const n = parseFloat(amount);
|
|
366
|
+
return !isNaN(n) && n > 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Parse a token amount to bigint with decimals
|
|
371
|
+
*/
|
|
372
|
+
export function parseTokenAmount(amount, decimals) {
|
|
373
|
+
return ethers.parseUnits(amount.toString(), decimals);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
// ──────────────────────────────────────────────────
|
|
378
|
+
// RETRY & TIMING HELPERS
|
|
379
|
+
// ──────────────────────────────────────────────────
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Sleep for a given number of milliseconds
|
|
383
|
+
*/
|
|
384
|
+
export function sleep(ms) {
|
|
385
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Retry a function with exponential backoff
|
|
390
|
+
* @param {Function} fn - Async function to retry
|
|
391
|
+
* @param {number} maxRetries - Max number of retries
|
|
392
|
+
* @param {number} baseDelay - Base delay in ms (doubles each retry)
|
|
393
|
+
* @returns {Promise<any>}
|
|
394
|
+
*/
|
|
395
|
+
export async function retry(fn, maxRetries = 3, baseDelay = 1000) {
|
|
396
|
+
let lastError;
|
|
397
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
398
|
+
try {
|
|
399
|
+
return await fn();
|
|
400
|
+
} catch (err) {
|
|
401
|
+
lastError = err;
|
|
402
|
+
if (i < maxRetries) {
|
|
403
|
+
const delay = baseDelay * Math.pow(2, i);
|
|
404
|
+
await sleep(delay);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
throw lastError;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Wait for a transaction with timeout
|
|
413
|
+
* @param {ethers.TransactionResponse} tx
|
|
414
|
+
* @param {number} timeoutMs - Timeout in ms (default 120s)
|
|
415
|
+
* @returns {Promise<ethers.TransactionReceipt>}
|
|
416
|
+
*/
|
|
417
|
+
export async function waitForTx(tx, timeoutMs = 120000) {
|
|
418
|
+
const receipt = await Promise.race([
|
|
419
|
+
tx.wait(),
|
|
420
|
+
new Promise((_, reject) =>
|
|
421
|
+
setTimeout(() => reject(new Error('Transaction timeout')), timeoutMs)
|
|
422
|
+
),
|
|
423
|
+
]);
|
|
424
|
+
return receipt;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
// ──────────────────────────────────────────────────
|
|
429
|
+
// DEX / PRICE HELPERS
|
|
430
|
+
// ──────────────────────────────────────────────────
|
|
431
|
+
|
|
432
|
+
const DEXSCREENER_API = 'https://api.dexscreener.com/latest';
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Quick price lookup via DexScreener
|
|
436
|
+
* @param {string} query - Token symbol or address
|
|
437
|
+
* @returns {Promise<{price: string, symbol: string, chain: string, liquidity: number} | null>}
|
|
438
|
+
*/
|
|
439
|
+
export async function quickPrice(query) {
|
|
440
|
+
try {
|
|
441
|
+
const resp = await fetch(`${DEXSCREENER_API}/dex/search?q=${encodeURIComponent(query)}`);
|
|
442
|
+
const data = await resp.json();
|
|
443
|
+
const pair = (data.pairs || [])
|
|
444
|
+
.sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
|
|
445
|
+
if (!pair) return null;
|
|
446
|
+
return {
|
|
447
|
+
price: pair.priceUsd,
|
|
448
|
+
symbol: pair.baseToken.symbol,
|
|
449
|
+
name: pair.baseToken.name,
|
|
450
|
+
chain: pair.chainId,
|
|
451
|
+
liquidity: pair.liquidity?.usd || 0,
|
|
452
|
+
volume24h: pair.volume?.h24 || 0,
|
|
453
|
+
change24h: pair.priceChange?.h24,
|
|
454
|
+
contract: pair.baseToken.address,
|
|
455
|
+
dex: pair.dexId,
|
|
456
|
+
};
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Check if a token has sufficient liquidity for trading
|
|
464
|
+
* @param {string} query - Token symbol or address
|
|
465
|
+
* @param {number} minLiquidity - Minimum liquidity in USD (default $1000)
|
|
466
|
+
*/
|
|
467
|
+
export async function hasLiquidity(query, minLiquidity = 1000) {
|
|
468
|
+
const data = await quickPrice(query);
|
|
469
|
+
if (!data) return false;
|
|
470
|
+
return data.liquidity >= minLiquidity;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
// ──────────────────────────────────────────────────
|
|
475
|
+
// DISPLAY HELPERS (CLI-specific)
|
|
476
|
+
// ──────────────────────────────────────────────────
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Show a transaction result card
|
|
480
|
+
*/
|
|
481
|
+
export function showTxResult(receipt, opts = {}) {
|
|
482
|
+
const chain = opts.chain || getConfig('chain');
|
|
483
|
+
|
|
484
|
+
showSection(opts.title || 'TRANSACTION RESULT');
|
|
485
|
+
kvDisplay([
|
|
486
|
+
['TX Hash', receipt.hash],
|
|
487
|
+
['Block', receipt.blockNumber.toString()],
|
|
488
|
+
['Gas Used', formatNumber(receipt.gasUsed.toString())],
|
|
489
|
+
['Status', receipt.status === 1 ? theme.success('✓ Success') : theme.error('✗ Failed')],
|
|
490
|
+
['Explorer', txUrl(receipt.hash, chain)],
|
|
491
|
+
]);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Show a wallet summary card
|
|
496
|
+
*/
|
|
497
|
+
export async function showWalletSummary(address, chain) {
|
|
498
|
+
const provider = getProvider(chain);
|
|
499
|
+
const spin = spinner('Fetching wallet info...').start();
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
const balance = await provider.getBalance(address);
|
|
503
|
+
const usdcAddr = getUSDC(chain);
|
|
504
|
+
let usdcBalance = '0.00';
|
|
505
|
+
|
|
506
|
+
if (usdcAddr) {
|
|
507
|
+
try {
|
|
508
|
+
const { formatted } = await getTokenBalance(usdcAddr, address, provider);
|
|
509
|
+
usdcBalance = formatted;
|
|
510
|
+
} catch {}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const nonce = await provider.getTransactionCount(address);
|
|
514
|
+
|
|
515
|
+
spin.succeed('Wallet loaded');
|
|
516
|
+
|
|
517
|
+
showSection('WALLET SUMMARY');
|
|
518
|
+
kvDisplay([
|
|
519
|
+
['Address', address],
|
|
520
|
+
['Chain', chain || getConfig('chain')],
|
|
521
|
+
['ETH', parseFloat(ethers.formatEther(balance)).toFixed(6)],
|
|
522
|
+
['USDC', `$${parseFloat(usdcBalance).toFixed(2)}`],
|
|
523
|
+
['TX Count', nonce.toString()],
|
|
524
|
+
['Explorer', addressUrl(address, chain)],
|
|
525
|
+
]);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
spin.fail('Failed');
|
|
528
|
+
error(err.message);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Show token info card
|
|
534
|
+
*/
|
|
535
|
+
export async function showTokenInfo(tokenAddress, chain) {
|
|
536
|
+
const provider = getProvider(chain);
|
|
537
|
+
const spin = spinner('Fetching token info...').start();
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const info_data = await getFullTokenInfo(tokenAddress, provider);
|
|
541
|
+
const priceData = await quickPrice(tokenAddress);
|
|
542
|
+
|
|
543
|
+
spin.succeed('Token loaded');
|
|
544
|
+
|
|
545
|
+
showSection(`${info_data.symbol} — ${info_data.name}`);
|
|
546
|
+
const pairs = [
|
|
547
|
+
['Contract', tokenAddress],
|
|
548
|
+
['Symbol', info_data.symbol],
|
|
549
|
+
['Name', info_data.name],
|
|
550
|
+
['Decimals', info_data.decimals.toString()],
|
|
551
|
+
['Total Supply', formatCompact(info_data.formattedSupply)],
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
if (priceData) {
|
|
555
|
+
pairs.push(
|
|
556
|
+
['Price', formatUSD(priceData.price)],
|
|
557
|
+
['24h Change', priceData.change24h ? `${priceData.change24h}%` : 'N/A'],
|
|
558
|
+
['Liquidity', formatUSD(priceData.liquidity)],
|
|
559
|
+
['Volume 24h', formatUSD(priceData.volume24h)],
|
|
560
|
+
['DEX', priceData.dex],
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
pairs.push(['Explorer', tokenUrl(tokenAddress, chain)]);
|
|
565
|
+
kvDisplay(pairs);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
spin.fail('Failed');
|
|
568
|
+
error(err.message);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
// ──────────────────────────────────────────────────
|
|
574
|
+
// TIPS & REFERENCE
|
|
575
|
+
// ──────────────────────────────────────────────────
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Show trading tips
|
|
579
|
+
*/
|
|
580
|
+
export function showTradingTips() {
|
|
581
|
+
showSection('TRADING TIPS');
|
|
582
|
+
const tips = [
|
|
583
|
+
['Slippage', 'Use 0.5% for stables, 1-3% for volatile tokens, 5%+ for micro-caps'],
|
|
584
|
+
['Gas Boost', 'Use 1.5-2x gas multiplier for snipes, 1.1x for normal swaps'],
|
|
585
|
+
['Approvals', 'First trade of a token requires an approve tx (one-time)'],
|
|
586
|
+
['Liquidity', 'Check liquidity before trading: darksol market token <SYMBOL>'],
|
|
587
|
+
['MEV', 'Large swaps on mainnet may get sandwiched. Use private RPCs or L2s'],
|
|
588
|
+
['Verify', 'Always verify contract addresses on block explorer before trading'],
|
|
589
|
+
['Test First', 'Test with small amounts before running large scripts'],
|
|
590
|
+
['Backup', 'Keep your wallet password backed up — no recovery if lost'],
|
|
591
|
+
['DCA', 'Dollar-cost averaging reduces timing risk: darksol dca create'],
|
|
592
|
+
['Stop Loss', 'Protect gains with stop-loss scripts: darksol script templates'],
|
|
593
|
+
];
|
|
594
|
+
|
|
595
|
+
tips.forEach(([label, tip]) => {
|
|
596
|
+
console.log(` ${theme.gold('◆')} ${theme.label(label.padEnd(12))} ${theme.dim(tip)}`);
|
|
597
|
+
});
|
|
598
|
+
console.log('');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Show script writing tips
|
|
603
|
+
*/
|
|
604
|
+
export function showScriptTips() {
|
|
605
|
+
showSection('SCRIPT WRITING TIPS');
|
|
606
|
+
const tips = [
|
|
607
|
+
['Context', 'Scripts get { signer, provider, ethers, config, params } — full access'],
|
|
608
|
+
['Signer', 'signer.address gives your wallet address, signer.sendTransaction() sends ETH'],
|
|
609
|
+
['ERC20', 'Use helpers: getERC20(address, signer) for token interactions'],
|
|
610
|
+
['Gas', 'Use getBoostedGas(provider, 1.5) for priority transactions'],
|
|
611
|
+
['Retry', 'Use retry(fn, 3, 1000) for unreliable RPC calls'],
|
|
612
|
+
['Sleep', 'Use sleep(ms) between polling iterations'],
|
|
613
|
+
['Validation', 'Use isValidAddress(), isValidAmount() to validate inputs'],
|
|
614
|
+
['Return', 'Return an object with results — it gets displayed after execution'],
|
|
615
|
+
['Errors', 'Throw errors to signal failure — they get caught and displayed'],
|
|
616
|
+
['Logging', 'Use console.log() inside scripts for live progress output'],
|
|
617
|
+
];
|
|
618
|
+
|
|
619
|
+
tips.forEach(([label, tip]) => {
|
|
620
|
+
console.log(` ${theme.gold('◆')} ${theme.label(label.padEnd(12))} ${theme.dim(tip)}`);
|
|
621
|
+
});
|
|
622
|
+
console.log('');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Show network reference
|
|
627
|
+
*/
|
|
628
|
+
export function showNetworkReference() {
|
|
629
|
+
showSection('NETWORK REFERENCE');
|
|
630
|
+
|
|
631
|
+
const rows = Object.entries(CHAIN_IDS).map(([chain, id]) => [
|
|
632
|
+
theme.gold(chain),
|
|
633
|
+
id.toString(),
|
|
634
|
+
EXPLORERS[chain],
|
|
635
|
+
getUSDC(chain) ? shortAddress(getUSDC(chain)) : theme.dim('N/A'),
|
|
636
|
+
]);
|
|
637
|
+
|
|
638
|
+
table(['Chain', 'ID', 'Explorer', 'USDC'], rows);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Show quick-start guide
|
|
643
|
+
*/
|
|
644
|
+
export function showQuickStart() {
|
|
645
|
+
showSection('QUICK START GUIDE');
|
|
646
|
+
|
|
647
|
+
console.log('');
|
|
648
|
+
console.log(theme.gold(' 1. Create a wallet'));
|
|
649
|
+
console.log(theme.dim(' darksol wallet create my-wallet'));
|
|
650
|
+
console.log('');
|
|
651
|
+
console.log(theme.gold(' 2. Fund it with ETH'));
|
|
652
|
+
console.log(theme.dim(' Send ETH to your wallet address on Base'));
|
|
653
|
+
console.log('');
|
|
654
|
+
console.log(theme.gold(' 3. Check balance'));
|
|
655
|
+
console.log(theme.dim(' darksol wallet balance'));
|
|
656
|
+
console.log('');
|
|
657
|
+
console.log(theme.gold(' 4. Look up a token'));
|
|
658
|
+
console.log(theme.dim(' darksol market token VIRTUAL'));
|
|
659
|
+
console.log('');
|
|
660
|
+
console.log(theme.gold(' 5. Swap tokens'));
|
|
661
|
+
console.log(theme.dim(' darksol trade swap -i ETH -o USDC -a 0.01'));
|
|
662
|
+
console.log('');
|
|
663
|
+
console.log(theme.gold(' 6. Create a trading script'));
|
|
664
|
+
console.log(theme.dim(' darksol script create'));
|
|
665
|
+
console.log('');
|
|
666
|
+
console.log(theme.gold(' 7. Set up DCA'));
|
|
667
|
+
console.log(theme.dim(' darksol dca create'));
|
|
668
|
+
console.log('');
|
|
669
|
+
console.log(theme.gold(' 8. Configure custom RPC'));
|
|
670
|
+
console.log(theme.dim(' darksol config rpc base https://your-rpc.com'));
|
|
671
|
+
console.log('');
|
|
672
|
+
|
|
673
|
+
info('Run any command with --help for full options');
|
|
674
|
+
info('Run darksol tips for trading tips');
|
|
675
|
+
info('Run darksol networks for chain reference');
|
|
676
|
+
console.log('');
|
|
677
|
+
}
|