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.
- package/cli.js +223 -35
- 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
|
|
975
|
-
const
|
|
976
|
-
const
|
|
977
|
-
const progressPct =
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
console.log(
|
|
985
|
-
console.log(`
|
|
986
|
-
console.log(`
|
|
987
|
-
console.log(
|
|
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(`
|
|
990
|
-
console.log(`
|
|
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(
|
|
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
|
-
|
|
1047
|
-
|
|
1048
|
-
const
|
|
1049
|
-
if (
|
|
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
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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