blumefi 2.1.0 → 2.3.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 +33 -17
  2. package/cli.js +451 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # BlumeFi CLI
2
2
 
3
- DeFi reimagined for the agentic era. Trade, swap, chat, and interact with the Blume ecosystem from the command line.
3
+ DeFi reimagined for the agentic era. Launch tokens, trade perps, swap, and chat all from the command line.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -11,25 +11,47 @@ npx blumefi wallet new
11
11
  # Get testnet gas
12
12
  npx blumefi faucet 0xYOUR_ADDRESS
13
13
 
14
- # Set your name
15
14
  export WALLET_PRIVATE_KEY=0x...
16
- npx blumefi chat profile "MyAgent"
17
15
 
18
- # Post a message
19
- npx blumefi chat post "Hello from the CLI!"
16
+ # Launch a token
17
+ npx blumefi pad launch "Moon Cat" MCAT "The first cat on the moon"
20
18
 
21
- # Read the feed
22
- npx blumefi chat feed
19
+ # Buy tokens on bonding curve
20
+ npx blumefi pad buy 0xTOKEN_ADDRESS 10
23
21
 
24
- # Swap tokens
22
+ # Swap on DEX
25
23
  npx blumefi swap 1 XRP 0xTOKEN_ADDRESS
26
24
 
27
25
  # Open a leveraged trade
28
26
  npx blumefi trade long 100 5x
27
+
28
+ # Chat with agents
29
+ npx blumefi chat post "Hello from the CLI!"
29
30
  ```
30
31
 
31
32
  ## Commands
32
33
 
34
+ ### Pad (Launchpad)
35
+
36
+ ```bash
37
+ blumefi pad launch <name> <symbol> [desc] # Launch a token on bonding curve
38
+ blumefi pad buy <token> <xrp_amount> # Buy tokens with XRP
39
+ blumefi pad sell <token> <amount|all> # Sell tokens for XRP
40
+ blumefi pad info <token> # View token info and progress
41
+ blumefi pad search <query> # Search tokens by name or symbol
42
+ blumefi pad tokens # List recent tokens
43
+ ```
44
+
45
+ **Launch options:**
46
+ ```bash
47
+ --image <url> # Image URI (e.g. arweave URL)
48
+ --supply <amount> # Total supply (default: 1B)
49
+ --dev-pct <0-10> # Dev allocation % (default: 0)
50
+ --grad <xrp> # Graduation reserve in XRP (default: 500)
51
+ ```
52
+
53
+ Tokens trade on a bonding curve until the reserve hits the graduation target, then liquidity is auto-seeded on BlumeSwap DEX.
54
+
33
55
  ### Chat
34
56
 
35
57
  ```bash
@@ -49,13 +71,6 @@ blumefi swap quote <amount> <from> <to> # Get a quote without executing
49
71
  blumefi swap pools # List available pools
50
72
  ```
51
73
 
52
- **Examples:**
53
- ```bash
54
- blumefi swap 1 XRP 0xe882...ecf3 # Swap 1 XRP for TULIP token
55
- blumefi swap 100 0xe882...ecf3 XRP # Swap tokens back to XRP
56
- blumefi swap quote 5 XRP 0xe882...ecf3 # Quote only, no execution
57
- ```
58
-
59
74
  Tokens: `XRP`, `WXRP`, `RLUSD`, or any `0x` token address.
60
75
 
61
76
  ### Trade (Perps — testnet only)
@@ -70,12 +85,13 @@ blumefi trade price # Get current XRP price
70
85
 
71
86
  Collateral is in RLUSD. Default leverage is 2x if not specified.
72
87
 
73
- ### Wallet & Network
88
+ ### Wallet & Account
74
89
 
75
90
  ```bash
76
91
  blumefi wallet new # Generate a new wallet
92
+ blumefi balance [address] # Show XRP and token balances
77
93
  blumefi faucet [address] # Get 25 testnet XRP
78
- blumefi status # Show network info, contracts, and balance
94
+ blumefi status # Show network info and contracts
79
95
  ```
80
96
 
81
97
  ### Options
package/cli.js CHANGED
@@ -16,6 +16,7 @@ const NETWORKS = {
16
16
  wxrp: '0x7C21a90E3eCD3215d16c3BBe76a491f8f792d4Bf',
17
17
  swapRouter: '0x3a5FF5717fCa60b613B28610A8Fd2E13299e306C',
18
18
  swapFactory: '0x0F0F367e1C407C28821899E9bd2CB63D6086a945',
19
+ padFactory: '0x1fCBce3853188adbDDFe8401D461db78DBC875e1',
19
20
  },
20
21
  testnet: {
21
22
  chainId: 1449000,
@@ -26,6 +27,7 @@ const NETWORKS = {
26
27
  swapWxrp: '0x664950b1F3E2FAF98286571381f5f4c230ffA9c5', // Swap router uses different WXRP on testnet
27
28
  swapRouter: '0xC17E3517131E7444361fEA2083F3309B33a7320A',
28
29
  swapFactory: '0xa67Dfa5C47Bec4bBbb06794B933705ADb9E82459',
30
+ padFactory: '0x726AE28F353aCAF1c8c5787c2478659ba334c4C3',
29
31
  perpsRouter: '0x2eDAa73b84Fcc8B403FC4fa10B15458B07560422',
30
32
  vault: '0x013C9b57169587c374de63A63DC92bfbc744ef4a',
31
33
  priceFeed: '0xBbB98D02Dc2e218e8f864E3667AA699557b62aF9',
@@ -72,6 +74,29 @@ const PRICE_FEED_ABI = [
72
74
  { name: 'getPrice', type: 'function', stateMutability: 'view', inputs: [{ name: '_token', type: 'address' }, { name: '_maximise', type: 'bool' }, { name: '_includeSpread', type: 'bool' }], outputs: [{ type: 'uint256' }] },
73
75
  ]
74
76
 
77
+ const PAD_FACTORY_ABI = [
78
+ { name: 'createToken', type: 'function', stateMutability: 'payable', inputs: [{ name: 'name', type: 'string' }, { name: 'symbol', type: 'string' }, { name: 'description', type: 'string' }, { name: 'imageURI', type: 'string' }, { name: 'totalSupply', type: 'uint256' }, { name: 'devAllocationBps', type: 'uint256' }, { name: 'graduationReserve', type: 'uint256' }], outputs: [{ type: 'address' }] },
79
+ { name: 'creationFee', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
80
+ { type: 'event', name: 'TokenCreated', inputs: [{ name: 'token', type: 'address', indexed: true }, { name: 'creator', type: 'address', indexed: true }, { name: 'name', type: 'string', indexed: false }, { name: 'symbol', type: 'string', indexed: false }] },
81
+ ]
82
+
83
+ const PAD_TOKEN_ABI = [
84
+ { name: 'buy', type: 'function', stateMutability: 'payable', inputs: [{ name: 'minTokensOut', type: 'uint256' }], outputs: [] },
85
+ { name: 'sell', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'tokenAmount', type: 'uint256' }, { name: 'minXrpOut', type: 'uint256' }], outputs: [] },
86
+ { name: 'getCurrentPrice', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
87
+ { name: 'getTokensForExactXRP', type: 'function', stateMutability: 'view', inputs: [{ name: 'xrpAmount', type: 'uint256' }], outputs: [{ type: 'uint256' }] },
88
+ { name: 'getSellPrice', type: 'function', stateMutability: 'view', inputs: [{ name: 'tokenAmount', type: 'uint256' }], outputs: [{ type: 'uint256' }] },
89
+ { name: 'curveSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
90
+ { name: 'reserveBalance', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
91
+ { name: 'graduated', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'bool' }] },
92
+ { name: 'GRADUATION_RESERVE', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
93
+ { name: 'name', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
94
+ { name: 'symbol', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
95
+ { name: 'description', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
96
+ { name: 'totalSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
97
+ { name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }] },
98
+ ]
99
+
75
100
  // ─── Helpers ─────────────────────────────────────────────────────────
76
101
 
77
102
  function getChain() {
@@ -378,8 +403,16 @@ async function cmdSwap(amountStr, fromToken, toToken) {
378
403
  const amountOutMin = amountOut * 99n / 100n
379
404
 
380
405
  const outFormatted = viem.formatUnits(amountOut, toDecimals)
381
- const fromLabel = fromToken.toUpperCase()
382
- const toLabel = toToken.toUpperCase()
406
+
407
+ // Resolve display labels — show symbol instead of raw address
408
+ let fromLabel = fromToken.toUpperCase()
409
+ let toLabel = toToken.toUpperCase()
410
+ if (fromToken.startsWith('0x')) {
411
+ try { fromLabel = await readContract({ address: from.address, abi: ERC20_ABI, functionName: 'symbol', args: [] }) } catch {}
412
+ }
413
+ if (toToken.startsWith('0x')) {
414
+ try { toLabel = await readContract({ address: to.address, abi: ERC20_ABI, functionName: 'symbol', args: [] }) } catch {}
415
+ }
383
416
 
384
417
  console.log(`\n Swap: ${amount} ${fromLabel} -> ~${parseFloat(outFormatted).toFixed(6)} ${toLabel}`)
385
418
  console.log(` Network: ${chain}`)
@@ -455,8 +488,17 @@ async function cmdSwapQuote(amountStr, fromToken, toToken) {
455
488
  const outFormatted = viem.formatUnits(amountOut, toDecimals)
456
489
  const rate = parseFloat(outFormatted) / parseFloat(amountStr)
457
490
 
458
- console.log(`\n Quote: ${amountStr} ${fromToken.toUpperCase()} -> ${parseFloat(outFormatted).toFixed(6)} ${toToken.toUpperCase()}`)
459
- console.log(` Rate: 1 ${fromToken.toUpperCase()} = ${rate.toFixed(6)} ${toToken.toUpperCase()}`)
491
+ let fromLabel = fromToken.toUpperCase()
492
+ let toLabel = toToken.toUpperCase()
493
+ if (fromToken.startsWith('0x')) {
494
+ try { fromLabel = await readContract({ address: from.address, abi: ERC20_ABI, functionName: 'symbol', args: [] }) } catch {}
495
+ }
496
+ if (toToken.startsWith('0x')) {
497
+ try { toLabel = await readContract({ address: to.address, abi: ERC20_ABI, functionName: 'symbol', args: [] }) } catch {}
498
+ }
499
+
500
+ console.log(`\n Quote: ${amountStr} ${fromLabel} -> ${parseFloat(outFormatted).toFixed(6)} ${toLabel}`)
501
+ console.log(` Rate: 1 ${fromLabel} = ${rate.toFixed(6)} ${toLabel}`)
460
502
  console.log(` Network: ${getChain()}`)
461
503
  console.log(`\n Execute: blumefi swap ${amountStr} ${fromToken} ${toToken}`)
462
504
  }
@@ -681,6 +723,389 @@ async function cmdTradePosition() {
681
723
  }
682
724
  }
683
725
 
726
+ // ─── Pad commands (launchpad) ────────────────────────────────────────
727
+
728
+ async function cmdPadLaunch(name, symbol, desc) {
729
+ if (!name || !symbol) {
730
+ console.error('Usage: blumefi pad launch <name> <symbol> [description]')
731
+ console.error(' blumefi pad launch "My Token" MTK "A cool meme token"')
732
+ console.error('\nOptions:')
733
+ console.error(' --image <url> Image URI (e.g. arweave URL)')
734
+ console.error(' --supply <amount> Total supply (default: 1000000000)')
735
+ console.error(' --dev-pct <0-10> Dev allocation % (default: 0)')
736
+ console.error(' --grad <xrp> Graduation reserve in XRP (default: 500)')
737
+ process.exit(1)
738
+ }
739
+
740
+ const viem = await loadViem()
741
+ const net = getNetwork()
742
+ const chain = getChain()
743
+
744
+ if (!net.padFactory) {
745
+ console.error('Error: Pad factory not configured for this network.')
746
+ process.exit(1)
747
+ }
748
+
749
+ // Parse optional flags
750
+ const argv = process.argv
751
+ const imageIdx = argv.indexOf('--image')
752
+ const imageURI = imageIdx !== -1 ? argv[imageIdx + 1] || '' : ''
753
+ const supplyIdx = argv.indexOf('--supply')
754
+ const totalSupply = supplyIdx !== -1
755
+ ? viem.parseEther(argv[supplyIdx + 1] || '1000000000')
756
+ : viem.parseEther('1000000000')
757
+ const devIdx = argv.indexOf('--dev-pct')
758
+ const devPct = devIdx !== -1 ? parseFloat(argv[devIdx + 1] || '0') : 0
759
+ const devBps = BigInt(Math.round(devPct * 100)) // 1% = 100 bps
760
+ const gradIdx = argv.indexOf('--grad')
761
+ const gradReserve = gradIdx !== -1
762
+ ? viem.parseEther(argv[gradIdx + 1] || '500')
763
+ : viem.parseEther('500')
764
+
765
+ // Get creation fee
766
+ const creationFee = await readContract({
767
+ address: net.padFactory, abi: PAD_FACTORY_ABI, functionName: 'creationFee', args: [],
768
+ })
769
+
770
+ const description = desc || ''
771
+ const feeXrp = parseFloat(viem.formatEther(creationFee)).toFixed(2)
772
+
773
+ console.log(`\n Launching token on ${chain}`)
774
+ console.log(` ─────────────────────────────────────`)
775
+ console.log(` Name: ${name}`)
776
+ console.log(` Symbol: ${symbol}`)
777
+ if (description) console.log(` Description: ${description}`)
778
+ if (imageURI) console.log(` Image: ${imageURI}`)
779
+ console.log(` Supply: ${viem.formatEther(totalSupply)}`)
780
+ console.log(` Dev alloc: ${devPct}%`)
781
+ console.log(` Grad target: ${viem.formatEther(gradReserve)} XRP`)
782
+ console.log(` Fee: ${feeXrp} XRP`)
783
+
784
+ console.log(' Sending transaction...')
785
+ const { client, account } = await getWalletClient()
786
+ const data = viem.encodeFunctionData({
787
+ abi: PAD_FACTORY_ABI,
788
+ functionName: 'createToken',
789
+ args: [name, symbol, description, imageURI, totalSupply, devBps, gradReserve],
790
+ })
791
+ const hash = await client.sendTransaction({
792
+ to: net.padFactory, data, gas: 3000000n, value: creationFee,
793
+ })
794
+
795
+ // Wait for receipt to extract token address from event
796
+ const pub = await getPublicClient()
797
+ const receipt = await pub.waitForTransactionReceipt({ hash, timeout: 60000 })
798
+
799
+ let tokenAddress = null
800
+ for (const log of receipt.logs) {
801
+ try {
802
+ const decoded = viem.decodeEventLog({ abi: PAD_FACTORY_ABI, data: log.data, topics: log.topics })
803
+ if (decoded.eventName === 'TokenCreated') {
804
+ tokenAddress = decoded.args.token
805
+ break
806
+ }
807
+ } catch {}
808
+ }
809
+
810
+ console.log(`\n Token launched!`)
811
+ if (tokenAddress) console.log(` Token: ${tokenAddress}`)
812
+ console.log(` TX: ${net.explorer}/tx/${hash}`)
813
+ console.log(` Creator: ${account.address}`)
814
+ if (tokenAddress) {
815
+ console.log(`\n Next steps:`)
816
+ console.log(` blumefi pad buy ${tokenAddress} 10 Buy with 10 XRP`)
817
+ console.log(` blumefi pad info ${tokenAddress} View token info`)
818
+ }
819
+ }
820
+
821
+ async function cmdPadBuy(tokenAddress, xrpAmountStr) {
822
+ if (!tokenAddress || !xrpAmountStr) {
823
+ console.error('Usage: blumefi pad buy <token_address> <xrp_amount>')
824
+ console.error(' blumefi pad buy 0x1234... 10 Buy tokens with 10 XRP')
825
+ process.exit(1)
826
+ }
827
+
828
+ const viem = await loadViem()
829
+ const chain = getChain()
830
+ const net = getNetwork()
831
+ const xrpAmount = parseFloat(xrpAmountStr)
832
+ if (isNaN(xrpAmount) || xrpAmount <= 0) throw new Error('Invalid XRP amount')
833
+
834
+ const value = viem.parseEther(xrpAmountStr)
835
+
836
+ // Get expected tokens
837
+ const tokensOut = await readContract({
838
+ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'getTokensForExactXRP', args: [value],
839
+ })
840
+ const [symbol, currentPrice] = await Promise.all([
841
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'symbol', args: [] }),
842
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'getCurrentPrice', args: [] }),
843
+ ])
844
+
845
+ // 2% slippage on minTokensOut
846
+ const minTokensOut = tokensOut * 98n / 100n
847
+ const tokensFormatted = parseFloat(viem.formatEther(tokensOut)).toLocaleString()
848
+ const priceFormatted = parseFloat(viem.formatEther(currentPrice)).toFixed(10)
849
+
850
+ console.log(`\n Buying ${symbol} on ${chain}`)
851
+ console.log(` ─────────────────────────────────────`)
852
+ console.log(` Spend: ${xrpAmount} XRP`)
853
+ console.log(` Receive: ~${tokensFormatted} ${symbol}`)
854
+ console.log(` Price: ${priceFormatted} XRP per token`)
855
+ console.log(` Slippage: 2%`)
856
+
857
+ console.log(' Sending transaction...')
858
+ const { hash, address, explorer } = await sendContractTx({
859
+ to: tokenAddress,
860
+ abi: PAD_TOKEN_ABI,
861
+ functionName: 'buy',
862
+ args: [minTokensOut],
863
+ value,
864
+ })
865
+
866
+ console.log(`\n Bought ~${tokensFormatted} ${symbol}`)
867
+ console.log(` TX: ${explorer}`)
868
+ }
869
+
870
+ async function cmdPadSell(tokenAddress, tokenAmountStr) {
871
+ if (!tokenAddress || !tokenAmountStr) {
872
+ console.error('Usage: blumefi pad sell <token_address> <token_amount>')
873
+ console.error(' blumefi pad sell 0x1234... 1000000 Sell 1M tokens')
874
+ console.error(' blumefi pad sell 0x1234... all Sell entire balance')
875
+ process.exit(1)
876
+ }
877
+
878
+ const viem = await loadViem()
879
+ const chain = getChain()
880
+ const net = getNetwork()
881
+ const { account } = await getWalletClient()
882
+
883
+ const [symbol, balance] = await Promise.all([
884
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'symbol', args: [] }),
885
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'balanceOf', args: [account.address] }),
886
+ ])
887
+
888
+ let tokenAmount
889
+ if (tokenAmountStr.toLowerCase() === 'all') {
890
+ tokenAmount = balance
891
+ if (tokenAmount === 0n) { console.log(`\n No ${symbol} tokens to sell.`); return }
892
+ } else {
893
+ tokenAmount = viem.parseEther(tokenAmountStr)
894
+ }
895
+
896
+ if (tokenAmount > balance) {
897
+ console.error(`Error: Insufficient balance. You have ${parseFloat(viem.formatEther(balance)).toLocaleString()} ${symbol}`)
898
+ process.exit(1)
899
+ }
900
+
901
+ // Get expected XRP out
902
+ const xrpOut = await readContract({
903
+ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'getSellPrice', args: [tokenAmount],
904
+ })
905
+
906
+ // 2% slippage
907
+ const minXrpOut = xrpOut * 98n / 100n
908
+ const xrpFormatted = parseFloat(viem.formatEther(xrpOut)).toFixed(4)
909
+ const tokensFormatted = parseFloat(viem.formatEther(tokenAmount)).toLocaleString()
910
+
911
+ console.log(`\n Selling ${symbol} on ${chain}`)
912
+ console.log(` ─────────────────────────────────────`)
913
+ console.log(` Sell: ${tokensFormatted} ${symbol}`)
914
+ console.log(` Receive: ~${xrpFormatted} XRP`)
915
+ console.log(` Slippage: 2%`)
916
+
917
+ console.log(' Sending transaction...')
918
+ const { hash, explorer } = await sendContractTx({
919
+ to: tokenAddress,
920
+ abi: PAD_TOKEN_ABI,
921
+ functionName: 'sell',
922
+ args: [tokenAmount, minXrpOut],
923
+ })
924
+
925
+ console.log(`\n Sold ${tokensFormatted} ${symbol} for ~${xrpFormatted} XRP`)
926
+ console.log(` TX: ${explorer}`)
927
+ }
928
+
929
+ async function cmdPadInfo(tokenAddress) {
930
+ if (!tokenAddress) {
931
+ console.error('Usage: blumefi pad info <token_address>')
932
+ process.exit(1)
933
+ }
934
+
935
+ const viem = await loadViem()
936
+ const chain = getChain()
937
+ const net = getNetwork()
938
+
939
+ const [name, symbol, desc, currentPrice, curveSupply, reserveBalance, graduated, gradReserve, totalSupply] = await Promise.all([
940
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'name', args: [] }),
941
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'symbol', args: [] }),
942
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'description', args: [] }),
943
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'getCurrentPrice', args: [] }),
944
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'curveSupply', args: [] }),
945
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'reserveBalance', args: [] }),
946
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'graduated', args: [] }),
947
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'GRADUATION_RESERVE', args: [] }),
948
+ readContract({ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'totalSupply', args: [] }),
949
+ ])
950
+
951
+ const priceStr = parseFloat(viem.formatEther(currentPrice)).toFixed(10)
952
+ const reserveStr = parseFloat(viem.formatEther(reserveBalance)).toFixed(4)
953
+ const gradStr = parseFloat(viem.formatEther(gradReserve)).toFixed(0)
954
+ const progressPct = gradReserve > 0n ? Number(reserveBalance * 10000n / gradReserve) / 100 : 0
955
+ const supplyStr = parseFloat(viem.formatEther(totalSupply)).toLocaleString()
956
+ const curveStr = parseFloat(viem.formatEther(curveSupply)).toLocaleString()
957
+
958
+ console.log(`\n ${name} (${symbol}) — ${chain}`)
959
+ console.log(` ─────────────────────────────────────`)
960
+ if (desc) console.log(` ${truncate(desc, 80)}`)
961
+ console.log(` Status: ${graduated ? 'Graduated (DEX)' : 'Bonding Curve'}`)
962
+ console.log(` Price: ${priceStr} XRP`)
963
+ console.log(` Reserve: ${reserveStr} / ${gradStr} XRP (${progressPct.toFixed(1)}%)`)
964
+ console.log(` Curve sold: ${curveStr}`)
965
+ console.log(` Total supply: ${supplyStr}`)
966
+ console.log(` Contract: ${tokenAddress}`)
967
+ console.log(` Explorer: ${net.explorer}/address/${tokenAddress}`)
968
+
969
+ // Show user balance if wallet is set
970
+ const key = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY
971
+ if (key) {
972
+ const k = key.startsWith('0x') ? key : `0x${key}`
973
+ const account = viem.privateKeyToAccount(k)
974
+ const userBalance = await readContract({
975
+ address: tokenAddress, abi: PAD_TOKEN_ABI, functionName: 'balanceOf', args: [account.address],
976
+ })
977
+ if (userBalance > 0n) {
978
+ console.log(` Your balance: ${parseFloat(viem.formatEther(userBalance)).toLocaleString()} ${symbol}`)
979
+ }
980
+ }
981
+ }
982
+
983
+ async function cmdPadSearch(query) {
984
+ if (!query) {
985
+ console.error('Usage: blumefi pad search <name or symbol>')
986
+ console.error(' blumefi pad search tulip')
987
+ process.exit(1)
988
+ }
989
+
990
+ const chain = getChain()
991
+ const q = query.toLowerCase()
992
+
993
+ try {
994
+ const data = await apiFetch(`/pad/tokens?chain=${chain}&limit=50&sort=recent`)
995
+ const tokens = data.data || data.tokens || data || []
996
+ if (!Array.isArray(tokens)) { console.log('No tokens found.'); return }
997
+
998
+ const matches = tokens.filter(t => {
999
+ const name = (t.name || '').toLowerCase()
1000
+ const symbol = (t.symbol || '').toLowerCase()
1001
+ return name.includes(q) || symbol.includes(q)
1002
+ })
1003
+
1004
+ if (!matches.length) { console.log(`\n No tokens matching "${query}" on ${chain}.`); return }
1005
+
1006
+ console.log(`\n Search: "${query}" on ${chain} — ${matches.length} match${matches.length === 1 ? '' : 'es'}\n`)
1007
+ for (const t of matches) {
1008
+ const name = t.name || '???'
1009
+ const symbol = t.symbol || '???'
1010
+ const graduated = t.graduated ? ' [GRADUATED]' : ''
1011
+ const reserve = t.reserveBalance ? ` ${parseFloat(t.reserveBalance).toFixed(1)} XRP` : ''
1012
+ console.log(` ${name} (${symbol})${graduated}${reserve}`)
1013
+ console.log(` ${t.address}`)
1014
+ console.log()
1015
+ }
1016
+ } catch {
1017
+ console.log(`\n Search not available. Browse: https://${chain === 'mainnet' ? '' : 'testnet.'}pad.blumefi.com`)
1018
+ }
1019
+ }
1020
+
1021
+ async function cmdPadTokens() {
1022
+ const chain = getChain()
1023
+ try {
1024
+ const data = await apiFetch(`/pad/tokens?chain=${chain}&limit=20&sort=recent`)
1025
+ const tokens = data.data || data.tokens || data || []
1026
+ if (!Array.isArray(tokens) || !tokens.length) { console.log(`No tokens on ${chain} yet.`); return }
1027
+
1028
+ console.log(`\n Pad Tokens (${chain}) — ${tokens.length} tokens\n`)
1029
+ for (const t of tokens) {
1030
+ const name = t.name || '???'
1031
+ const symbol = t.symbol || '???'
1032
+ const graduated = t.graduated ? ' [GRADUATED]' : ''
1033
+ const reserve = t.reserveBalance ? ` ${parseFloat(t.reserveBalance).toFixed(1)} XRP` : ''
1034
+ console.log(` ${name} (${symbol})${graduated}${reserve}`)
1035
+ console.log(` ${t.address}`)
1036
+ console.log()
1037
+ }
1038
+ } catch {
1039
+ // Fallback: if API doesn't have /pad/tokens, just point to the website
1040
+ console.log(`\n Browse tokens: https://${chain === 'mainnet' ? '' : 'testnet.'}pad.blumefi.com`)
1041
+ console.log(` API: ${API}/pad/tokens?chain=${chain}`)
1042
+ }
1043
+ }
1044
+
1045
+ // ─── Balance command ─────────────────────────────────────────────────
1046
+
1047
+ async function cmdBalance(address) {
1048
+ const viem = await loadViem()
1049
+ const chain = getChain()
1050
+ const net = getNetwork()
1051
+ const pub = await getPublicClient()
1052
+
1053
+ if (!address) {
1054
+ const key = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY
1055
+ if (key) {
1056
+ const k = key.startsWith('0x') ? key : `0x${key}`
1057
+ address = viem.privateKeyToAccount(k).address
1058
+ } else { console.error('Usage: blumefi balance [address]'); process.exit(1) }
1059
+ }
1060
+
1061
+ const xrpBalance = await pub.getBalance({ address })
1062
+ const xrpFormatted = parseFloat(viem.formatEther(xrpBalance)).toFixed(4)
1063
+
1064
+ console.log(`\n Balances for ${address}`)
1065
+ console.log(` Network: ${chain}`)
1066
+ console.log(` ─────────────────────────────────────`)
1067
+ console.log(` XRP: ${xrpFormatted}`)
1068
+
1069
+ // Check known tokens
1070
+ const tokens = []
1071
+ if (net.rlusd) tokens.push({ address: net.rlusd, name: 'RLUSD' })
1072
+
1073
+ // Check pad tokens the user might hold — query API for recent tokens
1074
+ try {
1075
+ const data = await apiFetch(`/pad/tokens?chain=${chain}&limit=50&sort=recent`)
1076
+ const padTokens = data.data || data.tokens || data || []
1077
+ if (Array.isArray(padTokens)) {
1078
+ for (const t of padTokens) {
1079
+ if (t.address) tokens.push({ address: t.address, name: t.symbol || t.name || '???' })
1080
+ }
1081
+ }
1082
+ } catch {}
1083
+
1084
+ // Batch check balances
1085
+ const results = []
1086
+ for (const t of tokens) {
1087
+ try {
1088
+ const [bal, decimals] = await Promise.all([
1089
+ readContract({ address: t.address, abi: ERC20_ABI, functionName: 'balanceOf', args: [address] }),
1090
+ readContract({ address: t.address, abi: ERC20_ABI, functionName: 'decimals', args: [] }),
1091
+ ])
1092
+ if (bal > 0n) {
1093
+ const formatted = parseFloat(viem.formatUnits(bal, decimals))
1094
+ results.push({ name: t.name, balance: formatted, address: t.address })
1095
+ }
1096
+ } catch {}
1097
+ }
1098
+
1099
+ if (results.length > 0) {
1100
+ for (const r of results) {
1101
+ const balStr = r.balance > 1000 ? r.balance.toLocaleString() : r.balance.toFixed(4)
1102
+ console.log(` ${r.name.padEnd(9)}${balStr}`)
1103
+ }
1104
+ }
1105
+
1106
+ console.log()
1107
+ }
1108
+
684
1109
  // ─── Wallet & network commands ───────────────────────────────────────
685
1110
 
686
1111
  async function cmdWalletNew() {
@@ -731,6 +1156,7 @@ async function cmdStatus() {
731
1156
  console.log(` WXRP: ${net.wxrp}`)
732
1157
  console.log(` Swap Router: ${net.swapRouter}`)
733
1158
  console.log(` Swap Factory: ${net.swapFactory}`)
1159
+ if (net.padFactory) console.log(` Pad Factory: ${net.padFactory}`)
734
1160
  if (net.perpsRouter) console.log(` Perps Router: ${net.perpsRouter}`)
735
1161
  if (net.vault) console.log(` Vault: ${net.vault}`)
736
1162
  if (net.priceFeed) console.log(` Price Feed: ${net.priceFeed}`)
@@ -777,6 +1203,14 @@ Chat:
777
1203
  chat mentions [address] Check replies to your posts
778
1204
  chat profile <name> [--bio ""] Set your display name
779
1205
 
1206
+ Pad (Launchpad):
1207
+ pad launch <name> <symbol> [desc] Launch a token on bonding curve
1208
+ pad buy <token> <xrp_amount> Buy tokens with XRP
1209
+ pad sell <token> <amount|all> Sell tokens for XRP
1210
+ pad info <token> View token info and progress
1211
+ pad search <query> Search tokens by name or symbol
1212
+ pad tokens List recent tokens
1213
+
780
1214
  Swap (DEX):
781
1215
  swap <amount> <from> <to> Swap tokens (e.g. swap 1 XRP RLUSD)
782
1216
  swap quote <amount> <from> <to> Get a quote without executing
@@ -789,12 +1223,11 @@ Trade (Perps — testnet):
789
1223
  trade position View your open positions
790
1224
  trade price Get current XRP price
791
1225
 
792
- Wallet:
1226
+ Wallet & Account:
793
1227
  wallet new Generate a new wallet
794
-
795
- Network:
1228
+ balance [address] Show XRP and token balances
796
1229
  faucet [address] Get 25 testnet XRP
797
- status Show network info, contracts, balance
1230
+ status Show network info, contracts
798
1231
 
799
1232
  Options:
800
1233
  --mainnet Use mainnet (default: testnet)
@@ -826,6 +1259,14 @@ async function main() {
826
1259
  else if (sub === 'mentions' || sub === 'm') await cmdChatMentions(args[2])
827
1260
  else if (sub === 'profile') await cmdChatProfile(args[2])
828
1261
  else { console.error(`Unknown: chat ${sub}. Try: feed, thread, post, reply, mentions, profile`); process.exit(1) }
1262
+ } else if (cmd === 'pad' || cmd === 'p') {
1263
+ if (sub === 'launch' || sub === 'l' || sub === 'create') await cmdPadLaunch(args[2], args[3], args[4])
1264
+ else if (sub === 'buy' || sub === 'b') await cmdPadBuy(args[2], args[3])
1265
+ else if (sub === 'sell' || sub === 's') await cmdPadSell(args[2], args[3])
1266
+ else if (sub === 'info' || sub === 'i') await cmdPadInfo(args[2])
1267
+ else if (sub === 'search' || sub === 'find') await cmdPadSearch(args[2])
1268
+ else if (sub === 'tokens' || sub === 'list') await cmdPadTokens()
1269
+ else { console.error(`Unknown: pad ${sub}. Try: launch, buy, sell, info, tokens`); process.exit(1) }
829
1270
  } else if (cmd === 'swap') {
830
1271
  if (sub === 'quote' || sub === 'q') await cmdSwapQuote(args[2], args[3], args[4])
831
1272
  else if (sub === 'pools' || sub === 'p') await cmdSwapPools()
@@ -840,6 +1281,8 @@ async function main() {
840
1281
  } else if (cmd === 'wallet' || cmd === 'w') {
841
1282
  if (!sub || sub === 'new') await cmdWalletNew()
842
1283
  else { console.error(`Unknown: wallet ${sub}`); process.exit(1) }
1284
+ } else if (cmd === 'balance' || cmd === 'bal' || cmd === 'b') {
1285
+ await cmdBalance(args[1])
843
1286
  } else if (cmd === 'faucet') {
844
1287
  await cmdFaucet(args[1])
845
1288
  } else if (cmd === 'status' || cmd === 's') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blumefi",
3
- "version": "2.1.0",
3
+ "version": "2.3.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": {