blumefi 2.6.1 → 2.8.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 +212 -2
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -62,7 +62,7 @@ const NETWORKS = {
62
62
  wxrp: '0x7C21a90E3eCD3215d16c3BBe76a491f8f792d4Bf',
63
63
  swapRouter: '0x3a5FF5717fCa60b613B28610A8Fd2E13299e306C',
64
64
  swapFactory: '0x0F0F367e1C407C28821899E9bd2CB63D6086a945',
65
- padFactory: '0x1fCBce3853188adbDDFe8401D461db78DBC875e1',
65
+ padFactory: '0x8681A2566E35b14196cAc0E349DB585992d313BA',
66
66
  },
67
67
  testnet: {
68
68
  chainId: 1449000,
@@ -73,7 +73,7 @@ const NETWORKS = {
73
73
  swapWxrp: '0x664950b1F3E2FAF98286571381f5f4c230ffA9c5', // Swap router uses different WXRP on testnet
74
74
  swapRouter: '0xC17E3517131E7444361fEA2083F3309B33a7320A',
75
75
  swapFactory: '0xa67Dfa5C47Bec4bBbb06794B933705ADb9E82459',
76
- padFactory: '0x726AE28F353aCAF1c8c5787c2478659ba334c4C3',
76
+ padFactory: '0xe7a942B532333761641f3326a4583956C557Bee2',
77
77
  perpsRouter: '0x2eDAa73b84Fcc8B403FC4fa10B15458B07560422',
78
78
  vault: '0x013C9b57169587c374de63A63DC92bfbc744ef4a',
79
79
  priceFeed: '0xBbB98D02Dc2e218e8f864E3667AA699557b62aF9',
@@ -102,6 +102,22 @@ const SWAP_ROUTER_ABI = [
102
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[]' }] },
103
103
  { name: 'getAmountsOut', type: 'function', stateMutability: 'view', inputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'path', type: 'address[]' }], outputs: [{ type: 'uint256[]' }] },
104
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' }] },
105
121
  ]
106
122
 
107
123
  const PERPS_ROUTER_ABI = [
@@ -585,6 +601,196 @@ async function cmdSwapPools() {
585
601
  }
586
602
  }
587
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
+
588
794
  // ─── Trade commands (perps) ──────────────────────────────────────────
589
795
 
590
796
  function requirePerps() {
@@ -1489,6 +1695,8 @@ Swap (DEX):
1489
1695
  swap <amount> <from> <to> Swap tokens (e.g. swap 1 XRP RLUSD)
1490
1696
  swap quote <amount> <from> <to> Get a quote without executing
1491
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
1492
1700
 
1493
1701
  Trade (Perps — testnet):
1494
1702
  trade long <usd> [leverage] Open long (e.g. trade long 100 5x)
@@ -1547,6 +1755,8 @@ async function main() {
1547
1755
  } else if (cmd === 'swap') {
1548
1756
  if (sub === 'quote' || sub === 'q') await cmdSwapQuote(args[2], args[3], args[4])
1549
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])
1550
1760
  else await cmdSwap(sub, args[2], args[3]) // swap <amount> <from> <to>
1551
1761
  } else if (cmd === 'trade' || cmd === 't') {
1552
1762
  if (sub === 'long' || sub === 'l') await cmdTradeOpen(true, args[2], args[3])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blumefi",
3
- "version": "2.6.1",
3
+ "version": "2.8.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": {