@darksol/terminal 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 +198 -0
- package/assets/darksol-banner.png +0 -0
- package/bin/darksol.js +5 -0
- package/package.json +39 -0
- package/src/cli.js +434 -0
- package/src/config/store.js +75 -0
- package/src/scripts/engine.js +718 -0
- package/src/services/builders.js +70 -0
- package/src/services/cards.js +67 -0
- package/src/services/casino.js +94 -0
- package/src/services/facilitator.js +60 -0
- package/src/services/market.js +179 -0
- package/src/services/oracle.js +92 -0
- package/src/trading/dca.js +249 -0
- package/src/trading/index.js +3 -0
- package/src/trading/snipe.js +195 -0
- package/src/trading/swap.js +233 -0
- package/src/ui/banner.js +60 -0
- package/src/ui/components.js +126 -0
- package/src/ui/theme.js +46 -0
- package/src/wallet/keystore.js +127 -0
- package/src/wallet/manager.js +287 -0
- package/tests/cli.test.js +72 -0
- package/tests/config.test.js +75 -0
- package/tests/dca.test.js +141 -0
- package/tests/keystore.test.js +94 -0
- package/tests/scripts.test.js +136 -0
- package/tests/trading.test.js +21 -0
- package/tests/ui.test.js +27 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { getServiceURL } from '../config/store.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { spinner, table, kvDisplay, error } from '../ui/components.js';
|
|
5
|
+
import { showSection } from '../ui/banner.js';
|
|
6
|
+
|
|
7
|
+
const getURL = () => getServiceURL('builders') || 'https://builders.darksol.net';
|
|
8
|
+
|
|
9
|
+
export async function buildersLeaderboard(opts = {}) {
|
|
10
|
+
const spin = spinner('Loading builder leaderboard...').start();
|
|
11
|
+
try {
|
|
12
|
+
const resp = await fetch(`${getURL()}/api/leaderboard?limit=${opts.limit || 20}`);
|
|
13
|
+
const data = await resp.json();
|
|
14
|
+
spin.succeed('Leaderboard loaded');
|
|
15
|
+
|
|
16
|
+
showSection('ERC-8021 BUILDER LEADERBOARD');
|
|
17
|
+
const builders = data.builders || data;
|
|
18
|
+
if (Array.isArray(builders)) {
|
|
19
|
+
const rows = builders.map((b, i) => [
|
|
20
|
+
`#${i + 1}`,
|
|
21
|
+
theme.gold(b.code || b.builderCode || '?'),
|
|
22
|
+
b.name || 'Unknown',
|
|
23
|
+
b.transactions?.toString() || '0',
|
|
24
|
+
b.volume ? `$${b.volume}` : 'N/A',
|
|
25
|
+
]);
|
|
26
|
+
table(['Rank', 'Code', 'Name', 'TXs', 'Volume'], rows);
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
spin.fail('Leaderboard failed');
|
|
30
|
+
error(err.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function buildersLookup(code) {
|
|
35
|
+
const spin = spinner(`Looking up builder: ${code}...`).start();
|
|
36
|
+
try {
|
|
37
|
+
const resp = await fetch(`${getURL()}/api/builders/${code}`);
|
|
38
|
+
const data = await resp.json();
|
|
39
|
+
spin.succeed('Builder found');
|
|
40
|
+
|
|
41
|
+
showSection(`BUILDER — ${code}`);
|
|
42
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, typeof v === 'object' ? JSON.stringify(v) : String(v)]));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
spin.fail('Builder not found');
|
|
45
|
+
error(err.message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function buildersFeed(opts = {}) {
|
|
50
|
+
const spin = spinner('Loading builder feed...').start();
|
|
51
|
+
try {
|
|
52
|
+
const resp = await fetch(`${getURL()}/api/feed?limit=${opts.limit || 20}`);
|
|
53
|
+
const data = await resp.json();
|
|
54
|
+
spin.succeed('Feed loaded');
|
|
55
|
+
|
|
56
|
+
showSection('BUILDER FEED');
|
|
57
|
+
const txs = data.transactions || data;
|
|
58
|
+
if (Array.isArray(txs)) {
|
|
59
|
+
const rows = txs.map(tx => [
|
|
60
|
+
tx.builderCode || '?',
|
|
61
|
+
tx.hash ? `${tx.hash.slice(0, 10)}...` : 'N/A',
|
|
62
|
+
tx.timestamp ? new Date(tx.timestamp).toLocaleString() : 'N/A',
|
|
63
|
+
]);
|
|
64
|
+
table(['Builder', 'TX Hash', 'Time'], rows);
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
spin.fail('Feed failed');
|
|
68
|
+
error(err.message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { getServiceURL } from '../config/store.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { spinner, kvDisplay, error, table } from '../ui/components.js';
|
|
5
|
+
import { showSection } from '../ui/banner.js';
|
|
6
|
+
|
|
7
|
+
const getURL = () => getServiceURL('cards') || 'https://acp.darksol.net/cards';
|
|
8
|
+
|
|
9
|
+
export async function cardsCatalog() {
|
|
10
|
+
const spin = spinner('Loading card catalog...').start();
|
|
11
|
+
try {
|
|
12
|
+
const resp = await fetch(`${getURL()}/api/cards/catalog`);
|
|
13
|
+
const data = await resp.json();
|
|
14
|
+
spin.succeed('Catalog loaded');
|
|
15
|
+
|
|
16
|
+
showSection('PREPAID CARDS');
|
|
17
|
+
const cards = data.providers || data;
|
|
18
|
+
if (Array.isArray(cards)) {
|
|
19
|
+
const rows = cards.map(c => [
|
|
20
|
+
theme.gold(c.name),
|
|
21
|
+
c.network || 'Visa/MC',
|
|
22
|
+
c.denominations?.join(', ') || 'Various',
|
|
23
|
+
c.region || 'Global',
|
|
24
|
+
]);
|
|
25
|
+
table(['Provider', 'Network', 'Amounts', 'Region'], rows);
|
|
26
|
+
} else {
|
|
27
|
+
kvDisplay(Object.entries(cards).map(([k, v]) => [k, String(v)]));
|
|
28
|
+
}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
spin.fail('Catalog failed');
|
|
31
|
+
error(err.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function cardsOrder(provider, amount) {
|
|
36
|
+
const spin = spinner('Processing card order...').start();
|
|
37
|
+
try {
|
|
38
|
+
const resp = await fetch(`${getURL()}/api/cards/order`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify({ provider, amount }),
|
|
42
|
+
});
|
|
43
|
+
const data = await resp.json();
|
|
44
|
+
spin.succeed('Order placed');
|
|
45
|
+
|
|
46
|
+
showSection('CARD ORDER');
|
|
47
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
spin.fail('Order failed');
|
|
50
|
+
error(err.message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function cardsStatus(orderId) {
|
|
55
|
+
const spin = spinner('Checking order...').start();
|
|
56
|
+
try {
|
|
57
|
+
const resp = await fetch(`${getURL()}/api/cards/status?orderId=${orderId}`);
|
|
58
|
+
const data = await resp.json();
|
|
59
|
+
spin.succeed('Status loaded');
|
|
60
|
+
|
|
61
|
+
showSection(`CARD ORDER — ${orderId}`);
|
|
62
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
63
|
+
} catch (err) {
|
|
64
|
+
spin.fail('Status check failed');
|
|
65
|
+
error(err.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { getServiceURL } from '../config/store.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { spinner, kvDisplay, success, error, table } from '../ui/components.js';
|
|
5
|
+
import { showSection } from '../ui/banner.js';
|
|
6
|
+
|
|
7
|
+
const getURL = () => getServiceURL('casino') || 'https://casino.darksol.net';
|
|
8
|
+
|
|
9
|
+
export async function casinoBet(game, opts = {}) {
|
|
10
|
+
const spin = spinner(`Placing ${game} bet...`).start();
|
|
11
|
+
try {
|
|
12
|
+
const resp = await fetch(`${getURL()}/api/bet`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
body: JSON.stringify({
|
|
16
|
+
game,
|
|
17
|
+
choice: opts.choice,
|
|
18
|
+
number: opts.number,
|
|
19
|
+
wallet: opts.wallet,
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
const data = await resp.json();
|
|
23
|
+
spin.succeed('Bet placed');
|
|
24
|
+
|
|
25
|
+
showSection(`CASINO — ${game.toUpperCase()}`);
|
|
26
|
+
kvDisplay([
|
|
27
|
+
['Game', game],
|
|
28
|
+
['Your Call', opts.choice || opts.number || 'N/A'],
|
|
29
|
+
['Result', data.result ? theme.gold.bold(data.result) : 'N/A'],
|
|
30
|
+
['Won', data.won ? theme.success.bold('YES!') : theme.error('No')],
|
|
31
|
+
['Payout', data.payout ? `$${data.payout}` : '$0'],
|
|
32
|
+
['TX', data.txHash || 'N/A'],
|
|
33
|
+
]);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
spin.fail('Bet failed');
|
|
36
|
+
error(err.message);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function casinoTables() {
|
|
41
|
+
const spin = spinner('Loading tables...').start();
|
|
42
|
+
try {
|
|
43
|
+
const resp = await fetch(`${getURL()}/api/tables`);
|
|
44
|
+
const data = await resp.json();
|
|
45
|
+
spin.succeed('Tables loaded');
|
|
46
|
+
|
|
47
|
+
showSection('CASINO TABLES');
|
|
48
|
+
const tables = data.tables || data;
|
|
49
|
+
if (Array.isArray(tables)) {
|
|
50
|
+
const rows = tables.map(t => [
|
|
51
|
+
theme.gold(t.name || t.game),
|
|
52
|
+
t.multiplier || 'N/A',
|
|
53
|
+
t.minBet || '$1',
|
|
54
|
+
t.status || 'Open',
|
|
55
|
+
]);
|
|
56
|
+
table(['Game', 'Multiplier', 'Min Bet', 'Status'], rows);
|
|
57
|
+
} else {
|
|
58
|
+
kvDisplay(Object.entries(tables).map(([k, v]) => [k, String(v)]));
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
spin.fail('Failed to load tables');
|
|
62
|
+
error(err.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function casinoStats() {
|
|
67
|
+
const spin = spinner('Loading stats...').start();
|
|
68
|
+
try {
|
|
69
|
+
const resp = await fetch(`${getURL()}/api/stats`);
|
|
70
|
+
const data = await resp.json();
|
|
71
|
+
spin.succeed('Stats loaded');
|
|
72
|
+
|
|
73
|
+
showSection('CASINO STATS');
|
|
74
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, typeof v === 'object' ? JSON.stringify(v) : String(v)]));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
spin.fail('Failed to load stats');
|
|
77
|
+
error(err.message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function casinoReceipt(id) {
|
|
82
|
+
const spin = spinner(`Loading receipt ${id}...`).start();
|
|
83
|
+
try {
|
|
84
|
+
const resp = await fetch(`${getURL()}/api/receipt/${id}`);
|
|
85
|
+
const data = await resp.json();
|
|
86
|
+
spin.succeed('Receipt loaded');
|
|
87
|
+
|
|
88
|
+
showSection(`CASINO RECEIPT — ${id}`);
|
|
89
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
90
|
+
} catch (err) {
|
|
91
|
+
spin.fail('Receipt not found');
|
|
92
|
+
error(err.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { getServiceURL } from '../config/store.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { spinner, kvDisplay, error } from '../ui/components.js';
|
|
5
|
+
import { showSection } from '../ui/banner.js';
|
|
6
|
+
|
|
7
|
+
const getURL = () => getServiceURL('facilitator') || 'https://facilitator.darksol.net';
|
|
8
|
+
|
|
9
|
+
export async function facilitatorHealth() {
|
|
10
|
+
const spin = spinner('Checking facilitator...').start();
|
|
11
|
+
try {
|
|
12
|
+
const resp = await fetch(`${getURL()}/api/health`);
|
|
13
|
+
const data = await resp.json();
|
|
14
|
+
spin.succeed('Facilitator online');
|
|
15
|
+
|
|
16
|
+
showSection('FACILITATOR STATUS');
|
|
17
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, typeof v === 'object' ? JSON.stringify(v) : String(v)]));
|
|
18
|
+
} catch (err) {
|
|
19
|
+
spin.fail('Facilitator unreachable');
|
|
20
|
+
error(err.message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function facilitatorVerify(payment) {
|
|
25
|
+
const spin = spinner('Verifying payment...').start();
|
|
26
|
+
try {
|
|
27
|
+
const resp = await fetch(`${getURL()}/api/verify`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify(typeof payment === 'string' ? JSON.parse(payment) : payment),
|
|
31
|
+
});
|
|
32
|
+
const data = await resp.json();
|
|
33
|
+
spin.succeed(data.valid ? 'Payment valid' : 'Payment invalid');
|
|
34
|
+
|
|
35
|
+
showSection('PAYMENT VERIFICATION');
|
|
36
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
37
|
+
} catch (err) {
|
|
38
|
+
spin.fail('Verification failed');
|
|
39
|
+
error(err.message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function facilitatorSettle(payment) {
|
|
44
|
+
const spin = spinner('Settling on-chain...').start();
|
|
45
|
+
try {
|
|
46
|
+
const resp = await fetch(`${getURL()}/api/settle`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: { 'Content-Type': 'application/json' },
|
|
49
|
+
body: JSON.stringify(typeof payment === 'string' ? JSON.parse(payment) : payment),
|
|
50
|
+
});
|
|
51
|
+
const data = await resp.json();
|
|
52
|
+
spin.succeed('Settlement complete');
|
|
53
|
+
|
|
54
|
+
showSection('SETTLEMENT');
|
|
55
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
spin.fail('Settlement failed');
|
|
58
|
+
error(err.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { theme } from '../ui/theme.js';
|
|
3
|
+
import { spinner, table, kvDisplay, formatPrice, formatChange } from '../ui/components.js';
|
|
4
|
+
import { showSection } from '../ui/banner.js';
|
|
5
|
+
import { getConfig } from '../config/store.js';
|
|
6
|
+
|
|
7
|
+
const COINGECKO_API = 'https://api.coingecko.com/api/v3';
|
|
8
|
+
const DEXSCREENER_API = 'https://api.dexscreener.com/latest';
|
|
9
|
+
|
|
10
|
+
// Top movers on a chain
|
|
11
|
+
export async function topMovers(chain, opts = {}) {
|
|
12
|
+
const spin = spinner('Fetching market data...').start();
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// DexScreener for Base/chain-specific data
|
|
16
|
+
const chainMap = { base: 'base', ethereum: 'ethereum', arbitrum: 'arbitrum', polygon: 'polygon' };
|
|
17
|
+
const dexChain = chainMap[chain || getConfig('chain')] || 'base';
|
|
18
|
+
|
|
19
|
+
const resp = await fetch(`${DEXSCREENER_API}/dex/tokens/trending/${dexChain}`, {
|
|
20
|
+
headers: { 'Accept': 'application/json' },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Fallback to search if trending endpoint doesn't exist
|
|
24
|
+
const searchResp = await fetch(`${DEXSCREENER_API}/dex/search?q=top%20${dexChain}`, {
|
|
25
|
+
headers: { 'Accept': 'application/json' },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let pairs = [];
|
|
29
|
+
if (searchResp.ok) {
|
|
30
|
+
const data = await searchResp.json();
|
|
31
|
+
pairs = (data.pairs || [])
|
|
32
|
+
.filter(p => p.chainId === dexChain)
|
|
33
|
+
.slice(0, opts.limit || 15);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
spin.succeed('Market data loaded');
|
|
37
|
+
|
|
38
|
+
if (pairs.length === 0) {
|
|
39
|
+
console.log(theme.dim(' No pairs found for this chain'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
showSection(`TOP MOVERS — ${dexChain.toUpperCase()}`);
|
|
44
|
+
|
|
45
|
+
const rows = pairs.map(p => [
|
|
46
|
+
theme.gold(p.baseToken?.symbol || '?'),
|
|
47
|
+
formatPrice(p.priceUsd),
|
|
48
|
+
formatChange(p.priceChange?.h24),
|
|
49
|
+
`$${formatCompact(p.volume?.h24)}`,
|
|
50
|
+
`$${formatCompact(p.liquidity?.usd)}`,
|
|
51
|
+
p.dexId || '?',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
table(['Token', 'Price', '24h %', 'Volume', 'Liquidity', 'DEX'], rows);
|
|
55
|
+
|
|
56
|
+
} catch (err) {
|
|
57
|
+
spin.fail('Failed to fetch market data');
|
|
58
|
+
console.log(theme.error(` ${err.message}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Token detail
|
|
63
|
+
export async function tokenDetail(query, opts = {}) {
|
|
64
|
+
const spin = spinner(`Looking up ${query}...`).start();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Try DexScreener first
|
|
68
|
+
const resp = await fetch(`${DEXSCREENER_API}/dex/search?q=${encodeURIComponent(query)}`);
|
|
69
|
+
const data = await resp.json();
|
|
70
|
+
const pairs = data.pairs || [];
|
|
71
|
+
|
|
72
|
+
if (pairs.length === 0) {
|
|
73
|
+
spin.fail('Token not found');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get the most liquid pair
|
|
78
|
+
const pair = pairs.sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
|
|
79
|
+
|
|
80
|
+
spin.succeed('Token found');
|
|
81
|
+
|
|
82
|
+
showSection(`${pair.baseToken.symbol} / ${pair.quoteToken.symbol}`);
|
|
83
|
+
kvDisplay([
|
|
84
|
+
['Name', pair.baseToken.name],
|
|
85
|
+
['Contract', pair.baseToken.address],
|
|
86
|
+
['Price', formatPrice(pair.priceUsd)],
|
|
87
|
+
['24h Change', formatChange(pair.priceChange?.h24)],
|
|
88
|
+
['6h Change', formatChange(pair.priceChange?.h6)],
|
|
89
|
+
['1h Change', formatChange(pair.priceChange?.h1)],
|
|
90
|
+
['Volume 24h', `$${formatCompact(pair.volume?.h24)}`],
|
|
91
|
+
['Liquidity', `$${formatCompact(pair.liquidity?.usd)}`],
|
|
92
|
+
['FDV', pair.fdv ? `$${formatCompact(pair.fdv)}` : 'N/A'],
|
|
93
|
+
['DEX', pair.dexId],
|
|
94
|
+
['Chain', pair.chainId],
|
|
95
|
+
['Pair', pair.pairAddress],
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
// Show additional pairs
|
|
99
|
+
if (pairs.length > 1) {
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(theme.dim(` ${pairs.length - 1} more pairs found across DEXes`));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Also get CoinGecko data if available
|
|
105
|
+
try {
|
|
106
|
+
const cgResp = await fetch(`${COINGECKO_API}/search?query=${encodeURIComponent(query)}`);
|
|
107
|
+
const cgData = await cgResp.json();
|
|
108
|
+
if (cgData.coins?.length > 0) {
|
|
109
|
+
const coin = cgData.coins[0];
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(theme.dim(` CoinGecko: ${coin.name} (${coin.symbol}) — Rank #${coin.market_cap_rank || 'N/A'}`));
|
|
112
|
+
}
|
|
113
|
+
} catch { }
|
|
114
|
+
|
|
115
|
+
} catch (err) {
|
|
116
|
+
spin.fail('Lookup failed');
|
|
117
|
+
console.log(theme.error(` ${err.message}`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Compare tokens
|
|
122
|
+
export async function compareTokens(tokens, opts = {}) {
|
|
123
|
+
const spin = spinner('Fetching comparison data...').start();
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const results = [];
|
|
127
|
+
|
|
128
|
+
for (const token of tokens) {
|
|
129
|
+
const resp = await fetch(`${DEXSCREENER_API}/dex/search?q=${encodeURIComponent(token)}`);
|
|
130
|
+
const data = await resp.json();
|
|
131
|
+
const pair = (data.pairs || [])
|
|
132
|
+
.sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))[0];
|
|
133
|
+
|
|
134
|
+
results.push(pair ? {
|
|
135
|
+
symbol: pair.baseToken.symbol,
|
|
136
|
+
price: pair.priceUsd,
|
|
137
|
+
change24h: pair.priceChange?.h24,
|
|
138
|
+
volume: pair.volume?.h24,
|
|
139
|
+
liquidity: pair.liquidity?.usd,
|
|
140
|
+
chain: pair.chainId,
|
|
141
|
+
} : {
|
|
142
|
+
symbol: token,
|
|
143
|
+
price: null,
|
|
144
|
+
change24h: null,
|
|
145
|
+
volume: null,
|
|
146
|
+
liquidity: null,
|
|
147
|
+
chain: 'N/A',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
spin.succeed('Comparison ready');
|
|
152
|
+
|
|
153
|
+
showSection('TOKEN COMPARISON');
|
|
154
|
+
|
|
155
|
+
const rows = results.map(r => [
|
|
156
|
+
theme.gold(r.symbol),
|
|
157
|
+
r.price ? formatPrice(r.price) : theme.dim('N/A'),
|
|
158
|
+
r.change24h !== null ? formatChange(r.change24h) : theme.dim('N/A'),
|
|
159
|
+
r.volume ? `$${formatCompact(r.volume)}` : theme.dim('N/A'),
|
|
160
|
+
r.liquidity ? `$${formatCompact(r.liquidity)}` : theme.dim('N/A'),
|
|
161
|
+
r.chain,
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
table(['Token', 'Price', '24h %', 'Volume', 'Liquidity', 'Chain'], rows);
|
|
165
|
+
|
|
166
|
+
} catch (err) {
|
|
167
|
+
spin.fail('Comparison failed');
|
|
168
|
+
console.log(theme.error(` ${err.message}`));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function formatCompact(num) {
|
|
173
|
+
if (!num) return '0';
|
|
174
|
+
num = parseFloat(num);
|
|
175
|
+
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
|
|
176
|
+
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
|
|
177
|
+
if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K';
|
|
178
|
+
return num.toFixed(2);
|
|
179
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { getServiceURL } from '../config/store.js';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { spinner, kvDisplay, success, error } from '../ui/components.js';
|
|
5
|
+
import { showSection } from '../ui/banner.js';
|
|
6
|
+
|
|
7
|
+
const getURL = () => getServiceURL('oracle') || 'https://acp.darksol.net/oracle';
|
|
8
|
+
|
|
9
|
+
export async function oracleFlip() {
|
|
10
|
+
const spin = spinner('Flipping coin...').start();
|
|
11
|
+
try {
|
|
12
|
+
const resp = await fetch(`${getURL()}/api/coin`);
|
|
13
|
+
const data = await resp.json();
|
|
14
|
+
spin.succeed('Coin flipped');
|
|
15
|
+
showSection('ORACLE — COIN FLIP');
|
|
16
|
+
kvDisplay([
|
|
17
|
+
['Result', theme.gold.bold(data.result || data.value)],
|
|
18
|
+
['Proof', data.proof || data.txHash || 'N/A'],
|
|
19
|
+
]);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
spin.fail('Oracle failed');
|
|
22
|
+
error(err.message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function oracleDice(sides = 6) {
|
|
27
|
+
const spin = spinner(`Rolling d${sides}...`).start();
|
|
28
|
+
try {
|
|
29
|
+
const resp = await fetch(`${getURL()}/api/dice?sides=${sides}`);
|
|
30
|
+
const data = await resp.json();
|
|
31
|
+
spin.succeed('Dice rolled');
|
|
32
|
+
showSection(`ORACLE — D${sides}`);
|
|
33
|
+
kvDisplay([
|
|
34
|
+
['Result', theme.gold.bold(data.result || data.value)],
|
|
35
|
+
['Sides', sides.toString()],
|
|
36
|
+
['Proof', data.proof || data.txHash || 'N/A'],
|
|
37
|
+
]);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
spin.fail('Oracle failed');
|
|
40
|
+
error(err.message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function oracleNumber(min = 1, max = 100) {
|
|
45
|
+
const spin = spinner(`Generating number ${min}-${max}...`).start();
|
|
46
|
+
try {
|
|
47
|
+
const resp = await fetch(`${getURL()}/api/number?min=${min}&max=${max}`);
|
|
48
|
+
const data = await resp.json();
|
|
49
|
+
spin.succeed('Number generated');
|
|
50
|
+
showSection('ORACLE — RANDOM NUMBER');
|
|
51
|
+
kvDisplay([
|
|
52
|
+
['Result', theme.gold.bold(data.result || data.value)],
|
|
53
|
+
['Range', `${min} — ${max}`],
|
|
54
|
+
['Proof', data.proof || data.txHash || 'N/A'],
|
|
55
|
+
]);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
spin.fail('Oracle failed');
|
|
58
|
+
error(err.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function oracleShuffle(items) {
|
|
63
|
+
const spin = spinner('Shuffling...').start();
|
|
64
|
+
try {
|
|
65
|
+
const resp = await fetch(`${getURL()}/api/shuffle`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { 'Content-Type': 'application/json' },
|
|
68
|
+
body: JSON.stringify({ items }),
|
|
69
|
+
});
|
|
70
|
+
const data = await resp.json();
|
|
71
|
+
spin.succeed('Shuffled');
|
|
72
|
+
showSection('ORACLE — SHUFFLE');
|
|
73
|
+
console.log(theme.gold(' Result: ') + (data.result || data.value || []).join(', '));
|
|
74
|
+
} catch (err) {
|
|
75
|
+
spin.fail('Oracle failed');
|
|
76
|
+
error(err.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function oracleHealth() {
|
|
81
|
+
const spin = spinner('Checking oracle...').start();
|
|
82
|
+
try {
|
|
83
|
+
const resp = await fetch(`${getURL()}/api/health`);
|
|
84
|
+
const data = await resp.json();
|
|
85
|
+
spin.succeed('Oracle online');
|
|
86
|
+
showSection('ORACLE STATUS');
|
|
87
|
+
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
spin.fail('Oracle unreachable');
|
|
90
|
+
error(err.message);
|
|
91
|
+
}
|
|
92
|
+
}
|