blumefi 2.5.0 → 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 (3) hide show
  1. package/README.md +3 -2
  2. package/cli.js +231 -36
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -13,8 +13,8 @@ npx blumefi faucet 0xYOUR_ADDRESS
13
13
 
14
14
  export WALLET_PRIVATE_KEY=0x...
15
15
 
16
- # Launch a token
17
- npx blumefi pad launch "Moon Cat" MCAT "The first cat on the moon"
16
+ # Launch a token (always include --image!)
17
+ npx blumefi pad launch "Moon Cat" MCAT "The first cat on the moon" --image https://example.com/cat.png
18
18
 
19
19
  # Buy tokens on bonding curve
20
20
  npx blumefi pad buy 0xTOKEN_ADDRESS 10
@@ -40,6 +40,7 @@ blumefi pad sell <token> <amount|all> # Sell tokens for XRP
40
40
  blumefi pad info <token> # View token info and progress
41
41
  blumefi pad search <query> # Search tokens by name or symbol
42
42
  blumefi pad tokens # List recent tokens
43
+ blumefi pad update-image <token> <url> # Update display image (creator only)
43
44
  ```
44
45
 
45
46
  **Launch options:**
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,
@@ -791,7 +835,11 @@ async function cmdPadLaunch(name, symbol, desc) {
791
835
  console.log(` Name: ${name}`)
792
836
  console.log(` Symbol: ${symbol}`)
793
837
  if (description) console.log(` Description: ${description}`)
794
- if (imageURI) console.log(` Image: ${imageURI}`)
838
+ if (imageURI) {
839
+ console.log(` Image: ${imageURI}`)
840
+ } else {
841
+ console.log(` Image: (none — consider adding --image <url>)`)
842
+ }
795
843
  console.log(` Supply: ${viem.formatEther(totalSupply)}`)
796
844
  console.log(` Dev alloc: ${devPct}%`)
797
845
  console.log(` Grad target: ${viem.formatEther(gradReserve)} XRP`)
@@ -829,6 +877,9 @@ async function cmdPadLaunch(name, symbol, desc) {
829
877
  console.log(` Creator: ${account.address}`)
830
878
  if (tokenAddress) {
831
879
  console.log(`\n Next steps:`)
880
+ if (!imageURI) {
881
+ console.log(` blumefi pad update-image ${tokenAddress} <url> Add an image`)
882
+ }
832
883
  console.log(` blumefi pad buy ${tokenAddress} 10 Buy with 10 XRP`)
833
884
  console.log(` blumefi pad info ${tokenAddress} View token info`)
834
885
  }
@@ -964,23 +1015,34 @@ async function cmdPadInfo(tokenAddress) {
964
1015
  readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'totalSupply', args: [] }),
965
1016
  ])
966
1017
 
967
- const priceStr = parseFloat(viem.formatEther(currentPrice)).toFixed(10)
968
- const reserveStr = parseFloat(viem.formatEther(reserveBalance)).toFixed(4)
969
- const gradStr = parseFloat(viem.formatEther(gradReserve)).toFixed(0)
970
- 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
971
1022
  const supplyStr = parseFloat(viem.formatEther(totalSupply)).toLocaleString()
972
1023
  const curveStr = parseFloat(viem.formatEther(curveSupply)).toLocaleString()
973
-
974
- console.log(`\n ${name} (${symbol}) — ${chain}`)
975
- console.log(` ─────────────────────────────────────`)
976
- if (desc) console.log(` ${truncate(desc, 80)}`)
977
- console.log(` Status: ${graduated ? 'Graduated (DEX)' : 'Bonding Curve'}`)
978
- console.log(` Price: ${priceStr} XRP`)
979
- console.log(` Reserve: ${reserveStr} / ${gradStr} XRP (${progressPct.toFixed(1)}%)`)
980
- 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('')
981
1037
  console.log(` Total supply: ${supplyStr}`)
982
- console.log(` Contract: ${tokenAddress}`)
983
- 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
+ }
984
1046
 
985
1047
  // Show user balance if wallet is set
986
1048
  const key = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY
@@ -991,9 +1053,11 @@ async function cmdPadInfo(tokenAddress) {
991
1053
  address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'balanceOf', args: [account.address],
992
1054
  })
993
1055
  if (userBalance > 0n) {
994
- 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}`)
995
1058
  }
996
1059
  }
1060
+ console.log('')
997
1061
  }
998
1062
 
999
1063
  async function cmdPadSearch(query) {
@@ -1034,30 +1098,153 @@ async function cmdPadSearch(query) {
1034
1098
  }
1035
1099
  }
1036
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
+
1037
1164
  async function cmdPadTokens() {
1038
1165
  const chain = getChain()
1039
- try {
1040
- const data = await apiFetch(`/pad/tokens?chain=${chain}&limit=20&sort=recent`)
1041
- const tokens = data.data || data.tokens || data || []
1042
- 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
+ }
1043
1187
 
1044
- console.log(`\n Pad Tokens (${chain}) — ${tokens.length} tokens\n`)
1045
- for (const t of tokens) {
1046
- const name = t.name || '???'
1047
- const symbol = t.symbol || '???'
1048
- const graduated = t.graduated ? ' [GRADUATED]' : ''
1049
- const reserve = t.reserveBalance ? ` ${parseFloat(t.reserveBalance).toFixed(1)} XRP` : ''
1050
- console.log(` ${name} (${symbol})${graduated}${reserve}`)
1051
- console.log(` ${t.address}`)
1052
- 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))
1053
1229
  }
1054
- } catch {
1055
- // Fallback: if API doesn't have /pad/tokens, just point to the website
1056
- console.log(`\n Browse tokens: https://${chain === 'mainnet' ? '' : 'testnet.'}pad.blumefi.com`)
1057
- console.log(` API: ${API}/pad/tokens?chain=${chain}`)
1058
1230
  }
1059
1231
  }
1060
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
+
1061
1248
  async function cmdPadUpdateImage(tokenAddress, imageUrl) {
1062
1249
  if (!tokenAddress || !imageUrl) {
1063
1250
  console.error('Usage: blumefi pad update-image <token_address> <image_url>')
@@ -1255,14 +1442,21 @@ Chat:
1255
1442
  chat profile <name> [options] Set your display name and avatar
1256
1443
 
1257
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
1258
1448
  pad launch <name> <symbol> [desc] Launch a token on bonding curve
1259
1449
  pad buy <token> <xrp_amount> Buy tokens with XRP
1260
1450
  pad sell <token> <amount|all> Sell tokens for XRP
1261
- pad info <token> View token info and progress
1262
1451
  pad search <query> Search tokens by name or symbol
1263
- pad tokens List recent tokens
1264
1452
  pad update-image <token> <url> Update token image (creator only)
1265
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
+
1266
1460
  Swap (DEX):
1267
1461
  swap <amount> <from> <to> Swap tokens (e.g. swap 1 XRP RLUSD)
1268
1462
  swap quote <amount> <from> <to> Get a quote without executing
@@ -1319,8 +1513,9 @@ async function main() {
1319
1513
  else if (sub === 'info' || sub === 'i') await cmdPadInfo(args[2])
1320
1514
  else if (sub === 'search' || sub === 'find') await cmdPadSearch(args[2])
1321
1515
  else if (sub === 'tokens' || sub === 'list') await cmdPadTokens()
1516
+ else if (sub === 'stats') await cmdPadStats()
1322
1517
  else if (sub === 'update-image') await cmdPadUpdateImage(args[2], args[3])
1323
- 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) }
1324
1519
  } else if (cmd === 'swap') {
1325
1520
  if (sub === 'quote' || sub === 'q') await cmdSwapQuote(args[2], args[3], args[4])
1326
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.0",
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": {