blumefi 2.5.1 → 2.6.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/cli.js +251 -35
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -7,6 +7,52 @@
|
|
|
7
7
|
|
|
8
8
|
const API = 'https://api.blumefi.com'
|
|
9
9
|
|
|
10
|
+
// ─── ANSI colors (zero deps) ───────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
const c = {
|
|
13
|
+
bold: s => `\x1b[1m${s}\x1b[22m`,
|
|
14
|
+
dim: s => `\x1b[2m${s}\x1b[22m`,
|
|
15
|
+
green: s => `\x1b[32m${s}\x1b[39m`,
|
|
16
|
+
yellow: s => `\x1b[33m${s}\x1b[39m`,
|
|
17
|
+
red: s => `\x1b[31m${s}\x1b[39m`,
|
|
18
|
+
white: s => `\x1b[97m${s}\x1b[39m`,
|
|
19
|
+
cyan: s => `\x1b[36m${s}\x1b[39m`,
|
|
20
|
+
brightGreen: s => `\x1b[92m${s}\x1b[39m`,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function progressBar(pct, width = 16) {
|
|
24
|
+
const clamped = Math.max(0, Math.min(pct || 0, 100))
|
|
25
|
+
const filled = Math.round((clamped / 100) * width)
|
|
26
|
+
const empty = width - filled
|
|
27
|
+
const colorFn = clamped < 25 ? c.dim : clamped < 75 ? c.yellow : c.green
|
|
28
|
+
return colorFn('█'.repeat(filled)) + c.dim('░'.repeat(empty))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fmtPrice(price) {
|
|
32
|
+
const n = parseFloat(price)
|
|
33
|
+
if (isNaN(n) || n === 0) return '0'
|
|
34
|
+
if (n < 0.000001) return n.toExponential(2)
|
|
35
|
+
if (n < 0.01) return n.toFixed(6).replace(/0+$/, '').replace(/\.$/, '.0')
|
|
36
|
+
if (n < 1) return n.toFixed(4)
|
|
37
|
+
return n.toFixed(2)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fmtNum(val) {
|
|
41
|
+
const n = parseFloat(val)
|
|
42
|
+
if (isNaN(n)) return '—'
|
|
43
|
+
return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function padRight(str, len) {
|
|
47
|
+
const visible = str.replace(/\x1b\[[0-9;]*m/g, '')
|
|
48
|
+
return str + ' '.repeat(Math.max(0, len - visible.length))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function padLeft(str, len) {
|
|
52
|
+
const visible = str.replace(/\x1b\[[0-9;]*m/g, '')
|
|
53
|
+
return ' '.repeat(Math.max(0, len - visible.length)) + str
|
|
54
|
+
}
|
|
55
|
+
|
|
10
56
|
const NETWORKS = {
|
|
11
57
|
mainnet: {
|
|
12
58
|
chainId: 1440000,
|
|
@@ -971,23 +1017,34 @@ async function cmdPadInfo(tokenAddress) {
|
|
|
971
1017
|
readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'totalSupply', args: [] }),
|
|
972
1018
|
])
|
|
973
1019
|
|
|
974
|
-
const
|
|
975
|
-
const
|
|
976
|
-
const
|
|
977
|
-
const progressPct =
|
|
1020
|
+
const priceVal = parseFloat(viem.formatEther(currentPrice))
|
|
1021
|
+
const reserveVal = parseFloat(viem.formatEther(reserveBalance))
|
|
1022
|
+
const gradVal = parseFloat(viem.formatEther(gradReserve))
|
|
1023
|
+
const progressPct = gradVal > 0 ? Math.min((reserveVal / gradVal) * 100, 100) : 0
|
|
978
1024
|
const supplyStr = parseFloat(viem.formatEther(totalSupply)).toLocaleString()
|
|
979
1025
|
const curveStr = parseFloat(viem.formatEther(curveSupply)).toLocaleString()
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
console.log(
|
|
985
|
-
console.log(`
|
|
986
|
-
console.log(`
|
|
987
|
-
console.log(
|
|
1026
|
+
const statusText = graduated
|
|
1027
|
+
? c.green('Graduated (DEX)')
|
|
1028
|
+
: c.yellow('Active (bonding curve)')
|
|
1029
|
+
|
|
1030
|
+
console.log('')
|
|
1031
|
+
console.log(c.bold(` ${symbol}`) + c.dim(` — ${name}`))
|
|
1032
|
+
console.log(c.dim(` ${tokenAddress} · ${chain}`))
|
|
1033
|
+
console.log('')
|
|
1034
|
+
console.log(` Price: ${c.white(fmtPrice(priceVal))} XRP`)
|
|
1035
|
+
console.log(` Reserve: ${c.white(fmtNum(reserveVal))} / ${fmtNum(gradVal)} XRP`)
|
|
1036
|
+
console.log(` Progress: ${progressBar(progressPct, 20)} ${progressPct >= 100 ? c.green(Math.round(progressPct) + '%') : c.yellow(Math.round(progressPct) + '%')}`)
|
|
1037
|
+
console.log(` Status: ${statusText}`)
|
|
1038
|
+
console.log('')
|
|
988
1039
|
console.log(` Total supply: ${supplyStr}`)
|
|
989
|
-
console.log(`
|
|
990
|
-
console.log(`
|
|
1040
|
+
console.log(` Curve supply: ${curveStr}`)
|
|
1041
|
+
console.log(` Contract: ${c.dim(tokenAddress)}`)
|
|
1042
|
+
console.log(` Explorer: ${c.dim(net.explorer + '/address/' + tokenAddress)}`)
|
|
1043
|
+
|
|
1044
|
+
if (desc) {
|
|
1045
|
+
console.log('')
|
|
1046
|
+
console.log(c.dim(` ${truncate(desc, 100)}`))
|
|
1047
|
+
}
|
|
991
1048
|
|
|
992
1049
|
// Show user balance if wallet is set
|
|
993
1050
|
const key = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY
|
|
@@ -998,9 +1055,11 @@ async function cmdPadInfo(tokenAddress) {
|
|
|
998
1055
|
address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'balanceOf', args: [account.address],
|
|
999
1056
|
})
|
|
1000
1057
|
if (userBalance > 0n) {
|
|
1001
|
-
console.log(
|
|
1058
|
+
console.log('')
|
|
1059
|
+
console.log(` Your balance: ${c.white(parseFloat(viem.formatEther(userBalance)).toLocaleString())} ${symbol}`)
|
|
1002
1060
|
}
|
|
1003
1061
|
}
|
|
1062
|
+
console.log('')
|
|
1004
1063
|
}
|
|
1005
1064
|
|
|
1006
1065
|
async function cmdPadSearch(query) {
|
|
@@ -1041,30 +1100,179 @@ async function cmdPadSearch(query) {
|
|
|
1041
1100
|
}
|
|
1042
1101
|
}
|
|
1043
1102
|
|
|
1103
|
+
function renderTokenTable(tokens, total, chain, filter, frame = 0) {
|
|
1104
|
+
const lines = []
|
|
1105
|
+
if (!tokens.length) {
|
|
1106
|
+
lines.push(c.dim(` No tokens found (${chain}, filter: ${filter})`))
|
|
1107
|
+
return lines.join('\n')
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const active = tokens.filter(t => !t.graduated).length
|
|
1111
|
+
const graduated = tokens.filter(t => t.graduated).length
|
|
1112
|
+
const totalVol = tokens.reduce((sum, t) => sum + (parseFloat(t.volume24h) || 0), 0)
|
|
1113
|
+
|
|
1114
|
+
// Header banner
|
|
1115
|
+
const title = 'BLUMEPAD'
|
|
1116
|
+
const netLabel = chain
|
|
1117
|
+
const summary = `${tokens.length} tokens · ${graduated} graduated · ${fmtNum(totalVol)} XRP 24h vol`
|
|
1118
|
+
const innerWidth = 53
|
|
1119
|
+
lines.push(' ' + c.dim('┌' + '─'.repeat(innerWidth) + '┐'))
|
|
1120
|
+
lines.push(' ' + c.dim('│') + ' ' + c.bold(title) + ' '.repeat(innerWidth - title.length - netLabel.length - 4) + c.dim(netLabel) + ' ' + c.dim('│'))
|
|
1121
|
+
lines.push(' ' + c.dim('│') + ' ' + c.dim(summary) + ' '.repeat(Math.max(0, innerWidth - summary.length - 4)) + ' ' + c.dim('│'))
|
|
1122
|
+
lines.push(' ' + c.dim('└' + '─'.repeat(innerWidth) + '┘'))
|
|
1123
|
+
lines.push('')
|
|
1124
|
+
|
|
1125
|
+
const cols = { symbol: 10, name: 20, price: 14, mcap: 12, vol: 12, progress: 26, status: 10 }
|
|
1126
|
+
|
|
1127
|
+
const header =
|
|
1128
|
+
padRight(c.dim('Symbol'), cols.symbol) +
|
|
1129
|
+
padRight(c.dim('Name'), cols.name) +
|
|
1130
|
+
padLeft(c.dim('Price (XRP)'), cols.price) + ' ' +
|
|
1131
|
+
padLeft(c.dim('MCap'), cols.mcap) + ' ' +
|
|
1132
|
+
padLeft(c.dim('24h Vol'), cols.vol) + ' ' +
|
|
1133
|
+
padRight(c.dim('Progress'), cols.progress) +
|
|
1134
|
+
padRight(c.dim('Status'), cols.status)
|
|
1135
|
+
|
|
1136
|
+
lines.push(' ' + header)
|
|
1137
|
+
lines.push(' ' + c.dim('─'.repeat(106)))
|
|
1138
|
+
|
|
1139
|
+
for (const t of tokens) {
|
|
1140
|
+
const pct = Math.min(t.progress || 0, 100)
|
|
1141
|
+
const hot = pct >= 75 && !t.graduated
|
|
1142
|
+
const pulse = hot && frame % 2 === 0
|
|
1143
|
+
|
|
1144
|
+
let bar, pctStr, status, indicator
|
|
1145
|
+
if (hot) {
|
|
1146
|
+
pctStr = `${Math.round(pct)}%`
|
|
1147
|
+
const barFn = pulse ? c.brightGreen : c.green
|
|
1148
|
+
const clamped = Math.max(0, Math.min(pct, 100))
|
|
1149
|
+
const filled = Math.round((clamped / 100) * 16)
|
|
1150
|
+
const empty = 16 - filled
|
|
1151
|
+
bar = barFn('█'.repeat(filled)) + c.dim('░'.repeat(empty)) + ' ' + (pulse ? c.bold(c.brightGreen(pctStr)) : c.bold(c.yellow(pctStr)))
|
|
1152
|
+
indicator = pulse ? ' ▲' : ''
|
|
1153
|
+
status = pulse ? c.bold(c.brightGreen('Hot')) : c.yellow('Active')
|
|
1154
|
+
} else {
|
|
1155
|
+
pctStr = `${Math.round(pct)}%`
|
|
1156
|
+
bar = progressBar(pct) + ' ' + (pct >= 100 ? c.green(pctStr) : c.yellow(pctStr))
|
|
1157
|
+
indicator = ''
|
|
1158
|
+
status = t.graduated ? c.green('Graduated') : c.yellow('Active')
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const row =
|
|
1162
|
+
padRight(c.bold(t.symbol || '???') + indicator, cols.symbol) +
|
|
1163
|
+
padRight((t.name || '—').slice(0, 18), cols.name) +
|
|
1164
|
+
padLeft(fmtPrice(t.price), cols.price) + ' ' +
|
|
1165
|
+
padLeft(fmtNum(t.marketCap), cols.mcap) + ' ' +
|
|
1166
|
+
padLeft(fmtNum(t.volume24h), cols.vol) + ' ' +
|
|
1167
|
+
padRight(bar, cols.progress) +
|
|
1168
|
+
padRight(status, cols.status)
|
|
1169
|
+
|
|
1170
|
+
lines.push(' ' + row)
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
lines.push('')
|
|
1174
|
+
if (total > tokens.length) {
|
|
1175
|
+
lines.push(c.dim(` Showing ${tokens.length} of ${total}. Use --limit to see more.`))
|
|
1176
|
+
lines.push('')
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
return lines.join('\n')
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1044
1182
|
async function cmdPadTokens() {
|
|
1045
1183
|
const chain = getChain()
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
const
|
|
1049
|
-
if (
|
|
1184
|
+
const argv = process.argv
|
|
1185
|
+
const flagVal = (name, fallback) => {
|
|
1186
|
+
const idx = argv.indexOf(name)
|
|
1187
|
+
if (idx === -1) return fallback
|
|
1188
|
+
const val = argv[idx + 1]
|
|
1189
|
+
return (val && !val.startsWith('--')) ? val : fallback
|
|
1190
|
+
}
|
|
1191
|
+
const filter = flagVal('--filter', 'all')
|
|
1192
|
+
const sort = flagVal('--sort', 'newest')
|
|
1193
|
+
const limit = flagVal('--limit', '20')
|
|
1194
|
+
const watchMode = argv.includes('--watch') || argv.includes('-w')
|
|
1195
|
+
|
|
1196
|
+
if (!watchMode) {
|
|
1197
|
+
// One-shot mode (existing behavior)
|
|
1198
|
+
const data = await apiFetch(`/pad/tokens?chain=${chain}&filter=${filter}&sort=${sort}&limit=${limit}&offset=0`)
|
|
1199
|
+
const tokens = data.tokens || data.data || []
|
|
1200
|
+
const total = data.total || tokens.length
|
|
1201
|
+
console.log('')
|
|
1202
|
+
console.log(renderTokenTable(tokens, total, chain, filter))
|
|
1203
|
+
return
|
|
1204
|
+
}
|
|
1050
1205
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1206
|
+
// Live dashboard mode
|
|
1207
|
+
const FETCH_INTERVAL = 5000 // fetch new data every 5s
|
|
1208
|
+
const RENDER_INTERVAL = 500 // redraw for animation every 500ms
|
|
1209
|
+
const hideCursor = () => process.stdout.write('\x1b[?25l')
|
|
1210
|
+
const showCursor = () => process.stdout.write('\x1b[?25h')
|
|
1211
|
+
const clearScreen = () => process.stdout.write('\x1b[2J\x1b[H')
|
|
1212
|
+
|
|
1213
|
+
let running = true
|
|
1214
|
+
const cleanup = () => {
|
|
1215
|
+
running = false
|
|
1216
|
+
showCursor()
|
|
1217
|
+
console.log(c.dim('\n Goodbye.\n'))
|
|
1218
|
+
process.exit(0)
|
|
1219
|
+
}
|
|
1220
|
+
process.on('SIGINT', cleanup)
|
|
1221
|
+
process.on('SIGTERM', cleanup)
|
|
1222
|
+
|
|
1223
|
+
hideCursor()
|
|
1224
|
+
|
|
1225
|
+
let cachedTokens = [], cachedTotal = 0, lastFetch = 0, fetchError = null, frame = 0
|
|
1226
|
+
|
|
1227
|
+
while (running) {
|
|
1228
|
+
// Fetch new data if stale
|
|
1229
|
+
const now = Date.now()
|
|
1230
|
+
if (now - lastFetch >= FETCH_INTERVAL) {
|
|
1231
|
+
try {
|
|
1232
|
+
const data = await apiFetch(`/pad/tokens?chain=${chain}&filter=${filter}&sort=${sort}&limit=${limit}&offset=0`)
|
|
1233
|
+
cachedTokens = data.tokens || data.data || []
|
|
1234
|
+
cachedTotal = data.total || cachedTokens.length
|
|
1235
|
+
fetchError = null
|
|
1236
|
+
} catch (err) {
|
|
1237
|
+
fetchError = err.message
|
|
1238
|
+
}
|
|
1239
|
+
lastFetch = Date.now()
|
|
1060
1240
|
}
|
|
1061
|
-
|
|
1062
|
-
//
|
|
1063
|
-
|
|
1064
|
-
console.log(
|
|
1241
|
+
|
|
1242
|
+
// Render current frame
|
|
1243
|
+
clearScreen()
|
|
1244
|
+
console.log('')
|
|
1245
|
+
if (fetchError && !cachedTokens.length) {
|
|
1246
|
+
console.log(c.red(` Error fetching data: ${fetchError}`) + '\n' + c.dim(' Will retry...'))
|
|
1247
|
+
} else {
|
|
1248
|
+
console.log(renderTokenTable(cachedTokens, cachedTotal, chain, filter, frame))
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const ts = new Date().toLocaleTimeString('en-US', { hour12: false })
|
|
1252
|
+
const nextFetchIn = Math.max(0, Math.ceil((FETCH_INTERVAL - (Date.now() - lastFetch)) / 1000))
|
|
1253
|
+
console.log('')
|
|
1254
|
+
console.log(c.dim(` Updated ${ts} · Refreshing in ${nextFetchIn}s · Ctrl+C to exit`))
|
|
1255
|
+
|
|
1256
|
+
frame++
|
|
1257
|
+
await new Promise(r => setTimeout(r, RENDER_INTERVAL))
|
|
1065
1258
|
}
|
|
1066
1259
|
}
|
|
1067
1260
|
|
|
1261
|
+
async function cmdPadStats() {
|
|
1262
|
+
const chain = getChain()
|
|
1263
|
+
const data = await apiFetch(`/pad/stats?chain=${chain}`)
|
|
1264
|
+
|
|
1265
|
+
console.log('')
|
|
1266
|
+
console.log(c.bold(' Blumepad Stats') + c.dim(` (${chain})`))
|
|
1267
|
+
console.log('')
|
|
1268
|
+
console.log(` Total tokens: ${c.white(String(data.totalTokens ?? 0))}`)
|
|
1269
|
+
console.log(` Active: ${c.yellow(String(data.activeTokens ?? 0))}`)
|
|
1270
|
+
console.log(` Graduated: ${c.green(String(data.graduatedTokens ?? 0))}`)
|
|
1271
|
+
console.log(` 24h Volume: ${c.white(fmtNum(data.totalVolume24h ?? 0))} XRP`)
|
|
1272
|
+
console.log(` 24h Trades: ${c.white(String(data.totalTrades24h ?? 0))}`)
|
|
1273
|
+
console.log('')
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1068
1276
|
async function cmdPadUpdateImage(tokenAddress, imageUrl) {
|
|
1069
1277
|
if (!tokenAddress || !imageUrl) {
|
|
1070
1278
|
console.error('Usage: blumefi pad update-image <token_address> <image_url>')
|
|
@@ -1262,14 +1470,21 @@ Chat:
|
|
|
1262
1470
|
chat profile <name> [options] Set your display name and avatar
|
|
1263
1471
|
|
|
1264
1472
|
Pad (Launchpad):
|
|
1473
|
+
pad tokens List tokens with graduation progress
|
|
1474
|
+
pad info <token> Detailed token view with progress bar
|
|
1475
|
+
pad stats Aggregate launchpad stats
|
|
1265
1476
|
pad launch <name> <symbol> [desc] Launch a token on bonding curve
|
|
1266
1477
|
pad buy <token> <xrp_amount> Buy tokens with XRP
|
|
1267
1478
|
pad sell <token> <amount|all> Sell tokens for XRP
|
|
1268
|
-
pad info <token> View token info and progress
|
|
1269
1479
|
pad search <query> Search tokens by name or symbol
|
|
1270
|
-
pad tokens List recent tokens
|
|
1271
1480
|
pad update-image <token> <url> Update token image (creator only)
|
|
1272
1481
|
|
|
1482
|
+
Pad Options:
|
|
1483
|
+
--filter <active|graduated|all> Filter tokens (default: all)
|
|
1484
|
+
--sort <newest|marketcap|progress|price> Sort order (default: newest)
|
|
1485
|
+
--limit <N> Number of tokens (default: 20)
|
|
1486
|
+
--watch, -w Live dashboard (auto-refresh every 5s)
|
|
1487
|
+
|
|
1273
1488
|
Swap (DEX):
|
|
1274
1489
|
swap <amount> <from> <to> Swap tokens (e.g. swap 1 XRP RLUSD)
|
|
1275
1490
|
swap quote <amount> <from> <to> Get a quote without executing
|
|
@@ -1326,8 +1541,9 @@ async function main() {
|
|
|
1326
1541
|
else if (sub === 'info' || sub === 'i') await cmdPadInfo(args[2])
|
|
1327
1542
|
else if (sub === 'search' || sub === 'find') await cmdPadSearch(args[2])
|
|
1328
1543
|
else if (sub === 'tokens' || sub === 'list') await cmdPadTokens()
|
|
1544
|
+
else if (sub === 'stats') await cmdPadStats()
|
|
1329
1545
|
else if (sub === 'update-image') await cmdPadUpdateImage(args[2], args[3])
|
|
1330
|
-
else { console.error(`Unknown: pad ${sub}. Try: launch, buy, sell, info, tokens, update-image`); process.exit(1) }
|
|
1546
|
+
else { console.error(`Unknown: pad ${sub}. Try: launch, buy, sell, info, tokens, stats, update-image`); process.exit(1) }
|
|
1331
1547
|
} else if (cmd === 'swap') {
|
|
1332
1548
|
if (sub === 'quote' || sub === 'q') await cmdSwapQuote(args[2], args[3], args[4])
|
|
1333
1549
|
else if (sub === 'pools' || sub === 'p') await cmdSwapPools()
|
package/package.json
CHANGED