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.
Files changed (2) hide show
  1. package/cli.js +251 -35
  2. 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 priceStr = parseFloat(viem.formatEther(currentPrice)).toFixed(10)
975
- const reserveStr = parseFloat(viem.formatEther(reserveBalance)).toFixed(4)
976
- const gradStr = parseFloat(viem.formatEther(gradReserve)).toFixed(0)
977
- const progressPct = gradReserve > 0n ? Number(reserveBalance * 10000n / gradReserve) / 100 : 0
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
- console.log(`\n ${name} (${symbol}) — ${chain}`)
982
- console.log(` ─────────────────────────────────────`)
983
- if (desc) console.log(` ${truncate(desc, 80)}`)
984
- console.log(` Status: ${graduated ? 'Graduated (DEX)' : 'Bonding Curve'}`)
985
- console.log(` Price: ${priceStr} XRP`)
986
- console.log(` Reserve: ${reserveStr} / ${gradStr} XRP (${progressPct.toFixed(1)}%)`)
987
- console.log(` Curve sold: ${curveStr}`)
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(` Contract: ${tokenAddress}`)
990
- console.log(` Explorer: ${net.explorer}/address/${tokenAddress}`)
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(` Your balance: ${parseFloat(viem.formatEther(userBalance)).toLocaleString()} ${symbol}`)
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
- try {
1047
- const data = await apiFetch(`/pad/tokens?chain=${chain}&limit=20&sort=recent`)
1048
- const tokens = data.data || data.tokens || data || []
1049
- if (!Array.isArray(tokens) || !tokens.length) { console.log(`No tokens on ${chain} yet.`); return }
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
- console.log(`\n Pad Tokens (${chain}) — ${tokens.length} tokens\n`)
1052
- for (const t of tokens) {
1053
- const name = t.name || '???'
1054
- const symbol = t.symbol || '???'
1055
- const graduated = t.graduated ? ' [GRADUATED]' : ''
1056
- const reserve = t.reserveBalance ? ` ${parseFloat(t.reserveBalance).toFixed(1)} XRP` : ''
1057
- console.log(` ${name} (${symbol})${graduated}${reserve}`)
1058
- console.log(` ${t.address}`)
1059
- console.log()
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
- } catch {
1062
- // Fallback: if API doesn't have /pad/tokens, just point to the website
1063
- console.log(`\n Browse tokens: https://${chain === 'mainnet' ? '' : 'testnet.'}pad.blumefi.com`)
1064
- console.log(` API: ${API}/pad/tokens?chain=${chain}`)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blumefi",
3
- "version": "2.5.1",
3
+ "version": "2.6.1",
4
4
  "description": "BlumeFi CLI — DeFi reimagined for the agentic era. Trade, chat, and interact with the Blume ecosystem from the command line.",
5
5
  "main": "cli.js",
6
6
  "bin": {