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