blumefi 2.6.0 → 2.7.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 +260 -22
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -16,6 +16,8 @@ const c = {
|
|
|
16
16
|
yellow: s => `\x1b[33m${s}\x1b[39m`,
|
|
17
17
|
red: s => `\x1b[31m${s}\x1b[39m`,
|
|
18
18
|
white: s => `\x1b[97m${s}\x1b[39m`,
|
|
19
|
+
cyan: s => `\x1b[36m${s}\x1b[39m`,
|
|
20
|
+
brightGreen: s => `\x1b[92m${s}\x1b[39m`,
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function progressBar(pct, width = 16) {
|
|
@@ -100,6 +102,22 @@ const SWAP_ROUTER_ABI = [
|
|
|
100
102
|
{ name: 'swapExactTokensForTokens', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'amountOutMin', type: 'uint256' }, { name: 'path', type: 'address[]' }, { name: 'to', type: 'address' }, { name: 'deadline', type: 'uint256' }], outputs: [{ type: 'uint256[]' }] },
|
|
101
103
|
{ name: 'getAmountsOut', type: 'function', stateMutability: 'view', inputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'path', type: 'address[]' }], outputs: [{ type: 'uint256[]' }] },
|
|
102
104
|
{ name: 'WETH', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
105
|
+
{ name: 'addLiquidityETH', type: 'function', stateMutability: 'payable', inputs: [{ name: 'token', type: 'address' }, { name: 'amountTokenDesired', type: 'uint256' }, { name: 'amountTokenMin', type: 'uint256' }, { name: 'amountETHMin', type: 'uint256' }, { name: 'to', type: 'address' }, { name: 'deadline', type: 'uint256' }], outputs: [{ name: 'amountToken', type: 'uint256' }, { name: 'amountETH', type: 'uint256' }, { name: 'liquidity', type: 'uint256' }] },
|
|
106
|
+
{ name: 'removeLiquidityETH', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'token', type: 'address' }, { name: 'liquidity', type: 'uint256' }, { name: 'amountTokenMin', type: 'uint256' }, { name: 'amountETHMin', type: 'uint256' }, { name: 'to', type: 'address' }, { name: 'deadline', type: 'uint256' }], outputs: [{ name: 'amountToken', type: 'uint256' }, { name: 'amountETH', type: 'uint256' }] },
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
const SWAP_FACTORY_ABI = [
|
|
110
|
+
{ name: 'getPair', type: 'function', stateMutability: 'view', inputs: [{ name: 'tokenA', type: 'address' }, { name: 'tokenB', type: 'address' }], outputs: [{ type: 'address' }] },
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
const SWAP_PAIR_ABI = [
|
|
114
|
+
{ name: 'getReserves', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: 'reserve0', type: 'uint112' }, { name: 'reserve1', type: 'uint112' }, { name: 'blockTimestampLast', type: 'uint32' }] },
|
|
115
|
+
{ name: 'token0', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
116
|
+
{ name: 'token1', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
117
|
+
{ name: 'totalSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
|
|
118
|
+
{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
119
|
+
{ name: 'approve', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
|
|
120
|
+
{ name: 'allowance', type: 'function', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
103
121
|
]
|
|
104
122
|
|
|
105
123
|
const PERPS_ROUTER_ABI = [
|
|
@@ -583,6 +601,196 @@ async function cmdSwapPools() {
|
|
|
583
601
|
}
|
|
584
602
|
}
|
|
585
603
|
|
|
604
|
+
async function cmdSwapAddLiquidity(tokenAddress, xrpAmountStr) {
|
|
605
|
+
if (!tokenAddress || !xrpAmountStr) {
|
|
606
|
+
console.error('Usage: blumefi swap add-liquidity <token_address> <xrp_amount>')
|
|
607
|
+
console.error(' blumefi swap add-liquidity 0x1234... 10 Add liquidity with 10 XRP')
|
|
608
|
+
console.error(' blumefi swap add-liquidity 0x1234... 10 --slippage 5')
|
|
609
|
+
process.exit(1)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const viem = await loadViem()
|
|
613
|
+
const net = getNetwork()
|
|
614
|
+
const chain = getChain()
|
|
615
|
+
const xrpAmount = parseFloat(xrpAmountStr)
|
|
616
|
+
if (isNaN(xrpAmount) || xrpAmount <= 0) throw new Error('Invalid XRP amount')
|
|
617
|
+
|
|
618
|
+
// Parse --slippage flag (default 2%)
|
|
619
|
+
const slippageIdx = process.argv.indexOf('--slippage')
|
|
620
|
+
const slippagePct = slippageIdx !== -1 ? parseFloat(process.argv[slippageIdx + 1] || '2') : 2
|
|
621
|
+
if (isNaN(slippagePct) || slippagePct <= 0 || slippagePct > 50) throw new Error('Slippage must be between 0 and 50')
|
|
622
|
+
const slippageBps = BigInt(Math.round(slippagePct * 100))
|
|
623
|
+
|
|
624
|
+
const wxrp = net.swapWxrp || net.wxrp
|
|
625
|
+
const xrpValue = viem.parseEther(xrpAmountStr)
|
|
626
|
+
|
|
627
|
+
// Get pair address
|
|
628
|
+
const pairAddress = await readContract({
|
|
629
|
+
address: net.swapFactory, abi: SWAP_FACTORY_ABI, functionName: 'getPair', args: [tokenAddress, wxrp],
|
|
630
|
+
})
|
|
631
|
+
const zeroPair = '0x0000000000000000000000000000000000000000'
|
|
632
|
+
if (!pairAddress || pairAddress === zeroPair) {
|
|
633
|
+
throw new Error(`No pool exists for this token/XRP pair. Create one on swap.blumefi.com first.`)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Get reserves and figure out which side is the token vs WXRP
|
|
637
|
+
const [reserves, token0Addr, tokenDecimals, tokenSymbol] = await Promise.all([
|
|
638
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'getReserves', args: [] }),
|
|
639
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'token0', args: [] }),
|
|
640
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'decimals', args: [] }),
|
|
641
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'symbol', args: [] }),
|
|
642
|
+
])
|
|
643
|
+
|
|
644
|
+
const isToken0 = token0Addr.toLowerCase() === tokenAddress.toLowerCase()
|
|
645
|
+
const tokenReserve = isToken0 ? reserves[0] : reserves[1]
|
|
646
|
+
const xrpReserve = isToken0 ? reserves[1] : reserves[0]
|
|
647
|
+
|
|
648
|
+
if (xrpReserve === 0n || tokenReserve === 0n) throw new Error('Pool has no liquidity')
|
|
649
|
+
|
|
650
|
+
// Calculate required token amount at current pool ratio
|
|
651
|
+
// tokenAmount = xrpAmount * tokenReserve / xrpReserve (+ 1 to round up)
|
|
652
|
+
const tokenAmountRaw = xrpValue * tokenReserve / xrpReserve
|
|
653
|
+
const tokenAmountDesired = tokenAmountRaw + 1n
|
|
654
|
+
|
|
655
|
+
const tokenFormatted = parseFloat(viem.formatUnits(tokenAmountDesired, tokenDecimals)).toLocaleString()
|
|
656
|
+
|
|
657
|
+
// Slippage minimums (from raw ratio, not bumped desired)
|
|
658
|
+
const amountTokenMin = tokenAmountRaw * (10000n - slippageBps) / 10000n
|
|
659
|
+
const amountETHMin = xrpValue * (10000n - slippageBps) / 10000n
|
|
660
|
+
|
|
661
|
+
console.log(`\n Adding liquidity on ${chain}`)
|
|
662
|
+
console.log(` ─────────────────────────────────────`)
|
|
663
|
+
console.log(` Pool: ${tokenSymbol} / XRP`)
|
|
664
|
+
console.log(` XRP: ${xrpAmount} XRP`)
|
|
665
|
+
console.log(` Token: ~${tokenFormatted} ${tokenSymbol}`)
|
|
666
|
+
console.log(` Slippage: ${slippagePct}%`)
|
|
667
|
+
|
|
668
|
+
// Approve token for router
|
|
669
|
+
await ensureApproval(tokenAddress, net.swapRouter, tokenAmountDesired)
|
|
670
|
+
|
|
671
|
+
const { account } = await getWalletClient()
|
|
672
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200) // 20 min
|
|
673
|
+
|
|
674
|
+
console.log(' Sending transaction...')
|
|
675
|
+
const { hash, explorer } = await sendContractTx({
|
|
676
|
+
to: net.swapRouter,
|
|
677
|
+
abi: SWAP_ROUTER_ABI,
|
|
678
|
+
functionName: 'addLiquidityETH',
|
|
679
|
+
args: [tokenAddress, tokenAmountDesired, amountTokenMin, amountETHMin, account.address, deadline],
|
|
680
|
+
value: xrpValue,
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
console.log(`\n Added liquidity: ${xrpAmount} XRP + ~${tokenFormatted} ${tokenSymbol}`)
|
|
684
|
+
console.log(` TX: ${explorer}`)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async function cmdSwapRemoveLiquidity(tokenAddress, lpAmountStr) {
|
|
688
|
+
if (!tokenAddress || !lpAmountStr) {
|
|
689
|
+
console.error('Usage: blumefi swap remove-liquidity <token_address> <lp_amount|all>')
|
|
690
|
+
console.error(' blumefi swap remove-liquidity 0x1234... all Remove all liquidity')
|
|
691
|
+
console.error(' blumefi swap remove-liquidity 0x1234... 5 Remove 5 LP tokens')
|
|
692
|
+
console.error(' blumefi swap remove-liquidity 0x1234... 5 --slippage 5')
|
|
693
|
+
process.exit(1)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const viem = await loadViem()
|
|
697
|
+
const net = getNetwork()
|
|
698
|
+
const chain = getChain()
|
|
699
|
+
const { account } = await getWalletClient()
|
|
700
|
+
|
|
701
|
+
// Parse --slippage flag (default 2%)
|
|
702
|
+
const slippageIdx = process.argv.indexOf('--slippage')
|
|
703
|
+
const slippagePct = slippageIdx !== -1 ? parseFloat(process.argv[slippageIdx + 1] || '2') : 2
|
|
704
|
+
if (isNaN(slippagePct) || slippagePct <= 0 || slippagePct > 50) throw new Error('Slippage must be between 0 and 50')
|
|
705
|
+
const slippageBps = BigInt(Math.round(slippagePct * 100))
|
|
706
|
+
|
|
707
|
+
const wxrp = net.swapWxrp || net.wxrp
|
|
708
|
+
|
|
709
|
+
// Get pair address
|
|
710
|
+
const pairAddress = await readContract({
|
|
711
|
+
address: net.swapFactory, abi: SWAP_FACTORY_ABI, functionName: 'getPair', args: [tokenAddress, wxrp],
|
|
712
|
+
})
|
|
713
|
+
const zeroPair = '0x0000000000000000000000000000000000000000'
|
|
714
|
+
if (!pairAddress || pairAddress === zeroPair) {
|
|
715
|
+
throw new Error(`No pool exists for this token/XRP pair.`)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Get LP balance, reserves, pair info
|
|
719
|
+
const [lpBalance, reserves, token0Addr, lpTotalSupply, tokenDecimals, tokenSymbol] = await Promise.all([
|
|
720
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'balanceOf', args: [account.address] }),
|
|
721
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'getReserves', args: [] }),
|
|
722
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'token0', args: [] }),
|
|
723
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'totalSupply', args: [] }),
|
|
724
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'decimals', args: [] }),
|
|
725
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'symbol', args: [] }),
|
|
726
|
+
])
|
|
727
|
+
|
|
728
|
+
let lpAmount
|
|
729
|
+
if (lpAmountStr.toLowerCase() === 'all') {
|
|
730
|
+
lpAmount = lpBalance
|
|
731
|
+
if (lpAmount === 0n) { console.log(`\n No LP tokens for ${tokenSymbol}/XRP pool.`); return }
|
|
732
|
+
} else {
|
|
733
|
+
lpAmount = viem.parseEther(lpAmountStr)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (lpAmount > lpBalance) {
|
|
737
|
+
console.error(`Error: Insufficient LP balance. You have ${parseFloat(viem.formatEther(lpBalance)).toFixed(6)} LP tokens`)
|
|
738
|
+
process.exit(1)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Estimate what you'll receive
|
|
742
|
+
const isToken0 = token0Addr.toLowerCase() === tokenAddress.toLowerCase()
|
|
743
|
+
const tokenReserve = isToken0 ? reserves[0] : reserves[1]
|
|
744
|
+
const xrpReserve = isToken0 ? reserves[1] : reserves[0]
|
|
745
|
+
|
|
746
|
+
if (lpTotalSupply === 0n) throw new Error('Pool total supply is zero')
|
|
747
|
+
|
|
748
|
+
const expectedToken = lpAmount * tokenReserve / lpTotalSupply
|
|
749
|
+
const expectedXrp = lpAmount * xrpReserve / lpTotalSupply
|
|
750
|
+
|
|
751
|
+
const tokenFormatted = parseFloat(viem.formatUnits(expectedToken, tokenDecimals)).toLocaleString()
|
|
752
|
+
const xrpFormatted = parseFloat(viem.formatEther(expectedXrp)).toFixed(4)
|
|
753
|
+
const lpFormatted = parseFloat(viem.formatEther(lpAmount)).toFixed(6)
|
|
754
|
+
|
|
755
|
+
// Slippage minimums
|
|
756
|
+
const amountTokenMin = expectedToken * (10000n - slippageBps) / 10000n
|
|
757
|
+
const amountETHMin = expectedXrp * (10000n - slippageBps) / 10000n
|
|
758
|
+
|
|
759
|
+
console.log(`\n Removing liquidity on ${chain}`)
|
|
760
|
+
console.log(` ─────────────────────────────────────`)
|
|
761
|
+
console.log(` Pool: ${tokenSymbol} / XRP`)
|
|
762
|
+
console.log(` LP tokens: ${lpFormatted}`)
|
|
763
|
+
console.log(` Receive: ~${tokenFormatted} ${tokenSymbol} + ~${xrpFormatted} XRP`)
|
|
764
|
+
console.log(` Slippage: ${slippagePct}%`)
|
|
765
|
+
|
|
766
|
+
// Approve LP token for router
|
|
767
|
+
const lpAllowance = await readContract({
|
|
768
|
+
address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'allowance', args: [account.address, net.swapRouter],
|
|
769
|
+
})
|
|
770
|
+
if (lpAllowance < lpAmount) {
|
|
771
|
+
console.log(' Approving LP tokens...')
|
|
772
|
+
const maxUint = 2n ** 256n - 1n
|
|
773
|
+
const { hash: approveHash } = await sendContractTx({
|
|
774
|
+
to: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'approve', args: [net.swapRouter, maxUint],
|
|
775
|
+
})
|
|
776
|
+
const pub = await getPublicClient()
|
|
777
|
+
await pub.waitForTransactionReceipt({ hash: approveHash, timeout: 30000 })
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200) // 20 min
|
|
781
|
+
|
|
782
|
+
console.log(' Sending transaction...')
|
|
783
|
+
const { hash, explorer } = await sendContractTx({
|
|
784
|
+
to: net.swapRouter,
|
|
785
|
+
abi: SWAP_ROUTER_ABI,
|
|
786
|
+
functionName: 'removeLiquidityETH',
|
|
787
|
+
args: [tokenAddress, lpAmount, amountTokenMin, amountETHMin, account.address, deadline],
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
console.log(`\n Removed liquidity: ~${tokenFormatted} ${tokenSymbol} + ~${xrpFormatted} XRP`)
|
|
791
|
+
console.log(` TX: ${explorer}`)
|
|
792
|
+
}
|
|
793
|
+
|
|
586
794
|
// ─── Trade commands (perps) ──────────────────────────────────────────
|
|
587
795
|
|
|
588
796
|
function requirePerps() {
|
|
@@ -1098,7 +1306,7 @@ async function cmdPadSearch(query) {
|
|
|
1098
1306
|
}
|
|
1099
1307
|
}
|
|
1100
1308
|
|
|
1101
|
-
function renderTokenTable(tokens, total, chain, filter) {
|
|
1309
|
+
function renderTokenTable(tokens, total, chain, filter, frame = 0) {
|
|
1102
1310
|
const lines = []
|
|
1103
1311
|
if (!tokens.length) {
|
|
1104
1312
|
lines.push(c.dim(` No tokens found (${chain}, filter: ${filter})`))
|
|
@@ -1136,12 +1344,28 @@ function renderTokenTable(tokens, total, chain, filter) {
|
|
|
1136
1344
|
|
|
1137
1345
|
for (const t of tokens) {
|
|
1138
1346
|
const pct = Math.min(t.progress || 0, 100)
|
|
1139
|
-
const
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1347
|
+
const hot = pct >= 75 && !t.graduated
|
|
1348
|
+
const pulse = hot && frame % 2 === 0
|
|
1349
|
+
|
|
1350
|
+
let bar, pctStr, status, indicator
|
|
1351
|
+
if (hot) {
|
|
1352
|
+
pctStr = `${Math.round(pct)}%`
|
|
1353
|
+
const barFn = pulse ? c.brightGreen : c.green
|
|
1354
|
+
const clamped = Math.max(0, Math.min(pct, 100))
|
|
1355
|
+
const filled = Math.round((clamped / 100) * 16)
|
|
1356
|
+
const empty = 16 - filled
|
|
1357
|
+
bar = barFn('█'.repeat(filled)) + c.dim('░'.repeat(empty)) + ' ' + (pulse ? c.bold(c.brightGreen(pctStr)) : c.bold(c.yellow(pctStr)))
|
|
1358
|
+
indicator = pulse ? ' ▲' : ''
|
|
1359
|
+
status = pulse ? c.bold(c.brightGreen('Hot')) : c.yellow('Active')
|
|
1360
|
+
} else {
|
|
1361
|
+
pctStr = `${Math.round(pct)}%`
|
|
1362
|
+
bar = progressBar(pct) + ' ' + (pct >= 100 ? c.green(pctStr) : c.yellow(pctStr))
|
|
1363
|
+
indicator = ''
|
|
1364
|
+
status = t.graduated ? c.green('Graduated') : c.yellow('Active')
|
|
1365
|
+
}
|
|
1142
1366
|
|
|
1143
1367
|
const row =
|
|
1144
|
-
padRight(c.bold(t.symbol || '???'), cols.symbol) +
|
|
1368
|
+
padRight(c.bold(t.symbol || '???') + indicator, cols.symbol) +
|
|
1145
1369
|
padRight((t.name || '—').slice(0, 18), cols.name) +
|
|
1146
1370
|
padLeft(fmtPrice(t.price), cols.price) + ' ' +
|
|
1147
1371
|
padLeft(fmtNum(t.marketCap), cols.mcap) + ' ' +
|
|
@@ -1186,7 +1410,8 @@ async function cmdPadTokens() {
|
|
|
1186
1410
|
}
|
|
1187
1411
|
|
|
1188
1412
|
// Live dashboard mode
|
|
1189
|
-
const
|
|
1413
|
+
const FETCH_INTERVAL = 5000 // fetch new data every 5s
|
|
1414
|
+
const RENDER_INTERVAL = 500 // redraw for animation every 500ms
|
|
1190
1415
|
const hideCursor = () => process.stdout.write('\x1b[?25l')
|
|
1191
1416
|
const showCursor = () => process.stdout.write('\x1b[?25h')
|
|
1192
1417
|
const clearScreen = () => process.stdout.write('\x1b[2J\x1b[H')
|
|
@@ -1203,30 +1428,39 @@ async function cmdPadTokens() {
|
|
|
1203
1428
|
|
|
1204
1429
|
hideCursor()
|
|
1205
1430
|
|
|
1431
|
+
let cachedTokens = [], cachedTotal = 0, lastFetch = 0, fetchError = null, frame = 0
|
|
1432
|
+
|
|
1206
1433
|
while (running) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1434
|
+
// Fetch new data if stale
|
|
1435
|
+
const now = Date.now()
|
|
1436
|
+
if (now - lastFetch >= FETCH_INTERVAL) {
|
|
1437
|
+
try {
|
|
1438
|
+
const data = await apiFetch(`/pad/tokens?chain=${chain}&filter=${filter}&sort=${sort}&limit=${limit}&offset=0`)
|
|
1439
|
+
cachedTokens = data.tokens || data.data || []
|
|
1440
|
+
cachedTotal = data.total || cachedTokens.length
|
|
1441
|
+
fetchError = null
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
fetchError = err.message
|
|
1444
|
+
}
|
|
1445
|
+
lastFetch = Date.now()
|
|
1215
1446
|
}
|
|
1216
1447
|
|
|
1448
|
+
// Render current frame
|
|
1217
1449
|
clearScreen()
|
|
1218
1450
|
console.log('')
|
|
1219
|
-
|
|
1451
|
+
if (fetchError && !cachedTokens.length) {
|
|
1452
|
+
console.log(c.red(` Error fetching data: ${fetchError}`) + '\n' + c.dim(' Will retry...'))
|
|
1453
|
+
} else {
|
|
1454
|
+
console.log(renderTokenTable(cachedTokens, cachedTotal, chain, filter, frame))
|
|
1455
|
+
}
|
|
1220
1456
|
|
|
1221
|
-
const
|
|
1222
|
-
const
|
|
1457
|
+
const ts = new Date().toLocaleTimeString('en-US', { hour12: false })
|
|
1458
|
+
const nextFetchIn = Math.max(0, Math.ceil((FETCH_INTERVAL - (Date.now() - lastFetch)) / 1000))
|
|
1223
1459
|
console.log('')
|
|
1224
|
-
console.log(c.dim(` Updated ${ts} · Refreshing in ${
|
|
1460
|
+
console.log(c.dim(` Updated ${ts} · Refreshing in ${nextFetchIn}s · Ctrl+C to exit`))
|
|
1225
1461
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
await new Promise(r => setTimeout(r, 100))
|
|
1229
|
-
}
|
|
1462
|
+
frame++
|
|
1463
|
+
await new Promise(r => setTimeout(r, RENDER_INTERVAL))
|
|
1230
1464
|
}
|
|
1231
1465
|
}
|
|
1232
1466
|
|
|
@@ -1461,6 +1695,8 @@ Swap (DEX):
|
|
|
1461
1695
|
swap <amount> <from> <to> Swap tokens (e.g. swap 1 XRP RLUSD)
|
|
1462
1696
|
swap quote <amount> <from> <to> Get a quote without executing
|
|
1463
1697
|
swap pools List available pools
|
|
1698
|
+
swap add-liquidity <token> <xrp> Add liquidity to a token/XRP pool
|
|
1699
|
+
swap remove-liquidity <token> <lp|all> Remove liquidity from a pool
|
|
1464
1700
|
|
|
1465
1701
|
Trade (Perps — testnet):
|
|
1466
1702
|
trade long <usd> [leverage] Open long (e.g. trade long 100 5x)
|
|
@@ -1519,6 +1755,8 @@ async function main() {
|
|
|
1519
1755
|
} else if (cmd === 'swap') {
|
|
1520
1756
|
if (sub === 'quote' || sub === 'q') await cmdSwapQuote(args[2], args[3], args[4])
|
|
1521
1757
|
else if (sub === 'pools' || sub === 'p') await cmdSwapPools()
|
|
1758
|
+
else if (sub === 'add-liquidity' || sub === 'al') await cmdSwapAddLiquidity(args[2], args[3])
|
|
1759
|
+
else if (sub === 'remove-liquidity' || sub === 'rl') await cmdSwapRemoveLiquidity(args[2], args[3])
|
|
1522
1760
|
else await cmdSwap(sub, args[2], args[3]) // swap <amount> <from> <to>
|
|
1523
1761
|
} else if (cmd === 'trade' || cmd === 't') {
|
|
1524
1762
|
if (sub === 'long' || sub === 'l') await cmdTradeOpen(true, args[2], args[3])
|
package/package.json
CHANGED