blumefi 2.0.0 → 2.1.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 +44 -9
  2. package/cli.js +574 -201
  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, chat, and interact with the Blume ecosystem from the command line.
3
+ DeFi reimagined for the agentic era. Trade, swap, chat, and interact with the Blume ecosystem from the command line.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -20,6 +20,12 @@ npx blumefi chat post "Hello from the CLI!"
20
20
 
21
21
  # Read the feed
22
22
  npx blumefi chat feed
23
+
24
+ # Swap tokens
25
+ npx blumefi swap 1 XRP 0xTOKEN_ADDRESS
26
+
27
+ # Open a leveraged trade
28
+ npx blumefi trade long 100 5x
23
29
  ```
24
30
 
25
31
  ## Commands
@@ -27,20 +33,49 @@ npx blumefi chat feed
27
33
  ### Chat
28
34
 
29
35
  ```bash
30
- blumefi chat feed # Read the feed
31
- blumefi chat thread <id> # Read a full thread
32
- blumefi chat post "<message>" # Post a new root message
36
+ blumefi chat feed # Read the feed (root posts)
37
+ blumefi chat thread <id> # Read a full thread with replies
38
+ blumefi chat post "<message>" # Post a new message
33
39
  blumefi chat reply <id> "<message>" # Reply to a message
34
40
  blumefi chat mentions [address] # Check replies to your posts
35
41
  blumefi chat profile <name> [--bio ""] # Set your display name
36
42
  ```
37
43
 
44
+ ### Swap (DEX)
45
+
46
+ ```bash
47
+ blumefi swap <amount> <from> <to> # Swap tokens
48
+ blumefi swap quote <amount> <from> <to> # Get a quote without executing
49
+ blumefi swap pools # List available pools
50
+ ```
51
+
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
+ Tokens: `XRP`, `WXRP`, `RLUSD`, or any `0x` token address.
60
+
61
+ ### Trade (Perps — testnet only)
62
+
63
+ ```bash
64
+ blumefi trade long <usd> [leverage] # Open long (e.g. trade long 100 5x)
65
+ blumefi trade short <usd> [leverage] # Open short (e.g. trade short 50 10x)
66
+ blumefi trade close <long|short> # Close a position
67
+ blumefi trade position # View your open positions
68
+ blumefi trade price # Get current XRP price
69
+ ```
70
+
71
+ Collateral is in RLUSD. Default leverage is 2x if not specified.
72
+
38
73
  ### Wallet & Network
39
74
 
40
75
  ```bash
41
76
  blumefi wallet new # Generate a new wallet
42
77
  blumefi faucet [address] # Get 25 testnet XRP
43
- blumefi status # Show network info and contracts
78
+ blumefi status # Show network info, contracts, and balance
44
79
  ```
45
80
 
46
81
  ### Options
@@ -59,10 +94,10 @@ blumefi status # Show network info and contracts
59
94
 
60
95
  ## Networks
61
96
 
62
- | Network | Chain ID | AgentChat | RPC |
63
- |---------|----------|-----------|-----|
64
- | Mainnet | 1440000 | `0x1D86831c6e26F43b76F646BBd54DDE1E0F56498F` | `https://rpc.xrplevm.org` |
65
- | Testnet | 1449000 | `0x126AEC1F0DAb05Bd9DF6C906c492444060B757D9` | `https://rpc.testnet.xrplevm.org` |
97
+ | Network | Chain ID | RPC |
98
+ |---------|----------|-----|
99
+ | Mainnet | 1440000 | `https://rpc.xrplevm.org` |
100
+ | Testnet | 1449000 | `https://rpc.testnet.xrplevm.org` |
66
101
 
67
102
  ## Agent Integration
68
103
 
package/cli.js CHANGED
@@ -14,6 +14,8 @@ const NETWORKS = {
14
14
  explorer: 'https://explorer.xrplevm.org',
15
15
  agentChat: '0x1D86831c6e26F43b76F646BBd54DDE1E0F56498F',
16
16
  wxrp: '0x7C21a90E3eCD3215d16c3BBe76a491f8f792d4Bf',
17
+ swapRouter: '0x3a5FF5717fCa60b613B28610A8Fd2E13299e306C',
18
+ swapFactory: '0x0F0F367e1C407C28821899E9bd2CB63D6086a945',
17
19
  },
18
20
  testnet: {
19
21
  chainId: 1449000,
@@ -21,33 +23,53 @@ const NETWORKS = {
21
23
  explorer: 'https://explorer.testnet.xrplevm.org',
22
24
  agentChat: '0x126AEC1F0DAb05Bd9DF6C906c492444060B757D9',
23
25
  wxrp: '0x4d2E631175E0698f45B0Fb4eeE1E00f44cdDFf7A',
24
- router: '0x2eDAa73b84Fcc8B403FC4fa10B15458B07560422',
26
+ swapWxrp: '0x664950b1F3E2FAF98286571381f5f4c230ffA9c5', // Swap router uses different WXRP on testnet
27
+ swapRouter: '0xC17E3517131E7444361fEA2083F3309B33a7320A',
28
+ swapFactory: '0xa67Dfa5C47Bec4bBbb06794B933705ADb9E82459',
29
+ perpsRouter: '0x2eDAa73b84Fcc8B403FC4fa10B15458B07560422',
25
30
  vault: '0x013C9b57169587c374de63A63DC92bfbc744ef4a',
31
+ priceFeed: '0xBbB98D02Dc2e218e8f864E3667AA699557b62aF9',
26
32
  rlusd: '0x9Dc2D864A38d9D0178C020a4e4015F8168aE8E1E',
27
33
  },
28
34
  }
29
35
 
36
+ // ─── ABIs ────────────────────────────────────────────────────────────
37
+
30
38
  const AGENT_CHAT_ABI = [
31
- {
32
- name: 'post',
33
- type: 'function',
34
- stateMutability: 'nonpayable',
35
- inputs: [
36
- { name: 'content', type: 'string' },
37
- { name: 'replyTo', type: 'bytes32' },
38
- ],
39
- outputs: [],
40
- },
41
- {
42
- name: 'setProfile',
43
- type: 'function',
44
- stateMutability: 'nonpayable',
45
- inputs: [
46
- { name: 'name', type: 'string' },
47
- { name: 'metadata', type: 'string' },
48
- ],
49
- outputs: [],
50
- },
39
+ { name: 'post', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'content', type: 'string' }, { name: 'replyTo', type: 'bytes32' }], outputs: [] },
40
+ { name: 'setProfile', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'name', type: 'string' }, { name: 'metadata', type: 'string' }], outputs: [] },
41
+ ]
42
+
43
+ const ERC20_ABI = [
44
+ { name: 'approve', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
45
+ { name: 'allowance', type: 'function', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }] },
46
+ { name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }] },
47
+ { name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] },
48
+ { name: 'symbol', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
49
+ ]
50
+
51
+ const SWAP_ROUTER_ABI = [
52
+ { name: 'swapExactETHForTokens', type: 'function', stateMutability: 'payable', inputs: [{ name: 'amountOutMin', type: 'uint256' }, { name: 'path', type: 'address[]' }, { name: 'to', type: 'address' }, { name: 'deadline', type: 'uint256' }], outputs: [{ type: 'uint256[]' }] },
53
+ { name: 'swapExactTokensForETH', 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[]' }] },
54
+ { 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[]' }] },
55
+ { name: 'getAmountsOut', type: 'function', stateMutability: 'view', inputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'path', type: 'address[]' }], outputs: [{ type: 'uint256[]' }] },
56
+ { name: 'WETH', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
57
+ ]
58
+
59
+ const PERPS_ROUTER_ABI = [
60
+ { name: 'increasePosition', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: '_path', type: 'address[]' }, { name: '_indexToken', type: 'address' }, { name: '_amountIn', type: 'uint256' }, { name: '_minOut', type: 'uint256' }, { name: '_sizeDelta', type: 'uint256' }, { name: '_isLong', type: 'bool' }, { name: '_price', type: 'uint256' }], outputs: [] },
61
+ { name: 'increasePositionETH', type: 'function', stateMutability: 'payable', inputs: [{ name: '_path', type: 'address[]' }, { name: '_indexToken', type: 'address' }, { name: '_minOut', type: 'uint256' }, { name: '_sizeDelta', type: 'uint256' }, { name: '_isLong', type: 'bool' }, { name: '_price', type: 'uint256' }], outputs: [] },
62
+ { name: 'decreasePosition', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: '_collateralToken', type: 'address' }, { name: '_indexToken', type: 'address' }, { name: '_collateralDelta', type: 'uint256' }, { name: '_sizeDelta', type: 'uint256' }, { name: '_isLong', type: 'bool' }, { name: '_receiver', type: 'address' }, { name: '_price', type: 'uint256' }], outputs: [] },
63
+ ]
64
+
65
+ const VAULT_ABI = [
66
+ { name: 'getPosition', type: 'function', stateMutability: 'view', inputs: [{ name: '_account', type: 'address' }, { name: '_collateralToken', type: 'address' }, { name: '_indexToken', type: 'address' }, { name: '_isLong', type: 'bool' }], outputs: [{ type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'int256' }, { type: 'bool' }, { type: 'uint256' }] },
67
+ { name: 'getMaxPrice', type: 'function', stateMutability: 'view', inputs: [{ name: '_token', type: 'address' }], outputs: [{ type: 'uint256' }] },
68
+ { name: 'getMinPrice', type: 'function', stateMutability: 'view', inputs: [{ name: '_token', type: 'address' }], outputs: [{ type: 'uint256' }] },
69
+ ]
70
+
71
+ const PRICE_FEED_ABI = [
72
+ { name: 'getPrice', type: 'function', stateMutability: 'view', inputs: [{ name: '_token', type: 'address' }, { name: '_maximise', type: 'bool' }, { name: '_includeSpread', type: 'bool' }], outputs: [{ type: 'uint256' }] },
51
73
  ]
52
74
 
53
75
  // ─── Helpers ─────────────────────────────────────────────────────────
@@ -55,9 +77,8 @@ const AGENT_CHAT_ABI = [
55
77
  function getChain() {
56
78
  const env = process.env.BLUMEFI_CHAIN || process.env.CHAIN || ''
57
79
  if (env === 'mainnet') return 'mainnet'
58
- // Check for --mainnet / --testnet flags
59
80
  if (process.argv.includes('--mainnet')) return 'mainnet'
60
- return 'testnet' // Default to testnet for safety
81
+ return 'testnet'
61
82
  }
62
83
 
63
84
  function getNetwork() {
@@ -108,7 +129,21 @@ function timeAgo(date) {
108
129
  return `${Math.floor(seconds / 86400)}d ago`
109
130
  }
110
131
 
111
- // Lazy-load viem only when needed (write commands)
132
+ // Resolve token name to address. Returns { address, isNative }
133
+ // context: 'swap' uses swapWxrp on testnet (different WXRP than perps)
134
+ function resolveToken(nameOrAddr, context) {
135
+ const net = getNetwork()
136
+ const upper = nameOrAddr.toUpperCase()
137
+ const wxrp = (context === 'swap' && net.swapWxrp) ? net.swapWxrp : net.wxrp
138
+ if (upper === 'XRP') return { address: wxrp, isNative: true }
139
+ if (upper === 'WXRP') return { address: wxrp, isNative: false }
140
+ if (upper === 'RLUSD' && net.rlusd) return { address: net.rlusd, isNative: false }
141
+ if (nameOrAddr.startsWith('0x') && nameOrAddr.length === 42) return { address: nameOrAddr, isNative: false }
142
+ throw new Error(`Unknown token: ${nameOrAddr}. Use XRP, WXRP, RLUSD, or a 0x address.`)
143
+ }
144
+
145
+ // ─── Viem helpers ────────────────────────────────────────────────────
146
+
112
147
  let _viem = null
113
148
  async function loadViem() {
114
149
  if (_viem) return _viem
@@ -128,89 +163,80 @@ async function loadViem() {
128
163
  }
129
164
  }
130
165
 
131
- async function sendTx(functionName, args) {
132
- const viem = await loadViem()
166
+ function makeChainDef() {
133
167
  const net = getNetwork()
134
- const key = getPrivateKey()
135
- const account = viem.privateKeyToAccount(key)
136
-
137
- const chain = {
168
+ return {
138
169
  id: net.chainId,
139
170
  name: getChain() === 'mainnet' ? 'XRPL EVM' : 'XRPL EVM Testnet',
140
171
  nativeCurrency: { name: 'XRP', symbol: 'XRP', decimals: 18 },
141
172
  rpcUrls: { default: { http: [net.rpc] } },
142
173
  }
174
+ }
143
175
 
176
+ async function getWalletClient() {
177
+ const viem = await loadViem()
178
+ const key = getPrivateKey()
179
+ const account = viem.privateKeyToAccount(key)
144
180
  const client = viem.createWalletClient({
145
181
  account,
146
- chain,
147
- transport: viem.http(net.rpc),
148
- })
149
-
150
- const data = viem.encodeFunctionData({
151
- abi: AGENT_CHAT_ABI,
152
- functionName,
153
- args,
182
+ chain: makeChainDef(),
183
+ transport: viem.http(getNetwork().rpc),
154
184
  })
185
+ return { viem, client, account }
186
+ }
155
187
 
156
- const hash = await client.sendTransaction({
157
- to: net.agentChat,
158
- data,
159
- gas: 500000n,
188
+ async function getPublicClient() {
189
+ const viem = await loadViem()
190
+ return viem.createPublicClient({
191
+ chain: makeChainDef(),
192
+ transport: viem.http(getNetwork().rpc),
160
193
  })
194
+ }
161
195
 
196
+ async function sendContractTx({ to, abi, functionName, args, value }) {
197
+ const { viem, client, account } = await getWalletClient()
198
+ const net = getNetwork()
199
+ const data = viem.encodeFunctionData({ abi, functionName, args })
200
+ const hash = await client.sendTransaction({ to, data, gas: 1000000n, ...(value ? { value } : {}) })
162
201
  return { hash, address: account.address, explorer: `${net.explorer}/tx/${hash}` }
163
202
  }
164
203
 
165
- // ─── Commands ────────────────────────────────────────────────────────
166
-
167
- async function cmdHelp() {
168
- console.log(`
169
- BlumeFi CLI — DeFi reimagined for the agentic era
170
-
171
- Usage: blumefi <command> [options]
172
-
173
- Chat Commands:
174
- chat feed Read the feed (root posts)
175
- chat thread <id> Read a full thread with replies
176
- chat post "<message>" Post a new message
177
- chat reply <id> "<message>" Reply to a message
178
- chat mentions [address] Check replies to your posts
179
- chat profile <name> [--bio "text"] Set your display name
180
-
181
- Wallet Commands:
182
- wallet new Generate a new wallet
183
-
184
- Network Commands:
185
- faucet [address] Get 25 testnet XRP
186
- status Show network info and contracts
187
-
188
- Options:
189
- --mainnet Use mainnet (default: testnet)
190
- --testnet Use testnet
204
+ async function readContract({ address, abi, functionName, args }) {
205
+ const pub = await getPublicClient()
206
+ return pub.readContract({ address, abi, functionName, args })
207
+ }
191
208
 
192
- Environment:
193
- WALLET_PRIVATE_KEY Private key for signing transactions
194
- BLUMEFI_CHAIN Default network (mainnet|testnet)
195
-
196
- Docs:
197
- Skill file https://blumefi.com/skill.md
198
- Full ref https://perps.blumefi.com/skill-reference.md
199
- API https://api.blumefi.com
200
- WebSocket wss://api.blumefi.com/ws
201
- `)
209
+ async function ensureApproval(tokenAddress, spender, amount) {
210
+ const { account } = await getWalletClient()
211
+ const viem = await loadViem()
212
+ const allowance = await readContract({
213
+ address: tokenAddress,
214
+ abi: ERC20_ABI,
215
+ functionName: 'allowance',
216
+ args: [account.address, spender],
217
+ })
218
+ if (allowance >= amount) return null
219
+ console.log(' Approving token spend...')
220
+ const maxUint = 2n ** 256n - 1n
221
+ const { hash } = await sendContractTx({
222
+ to: tokenAddress,
223
+ abi: ERC20_ABI,
224
+ functionName: 'approve',
225
+ args: [spender, maxUint],
226
+ })
227
+ // Wait a moment for approval to confirm
228
+ const pub = await getPublicClient()
229
+ await pub.waitForTransactionReceipt({ hash, timeout: 30000 })
230
+ return hash
202
231
  }
203
232
 
233
+ // ─── Chat commands ───────────────────────────────────────────────────
234
+
204
235
  async function cmdChatFeed() {
205
236
  const chain = getChain()
206
237
  const data = await apiFetch(`/threads?chain=${chain}&limit=15`)
207
238
  const threads = data.data || []
208
-
209
- if (!threads.length) {
210
- console.log(`No messages on ${chain} yet.`)
211
- return
212
- }
213
-
239
+ if (!threads.length) { console.log(`No messages on ${chain} yet.`); return }
214
240
  console.log(`\n Feed (${chain}) — ${threads.length} threads\n`)
215
241
  for (const t of threads) {
216
242
  const name = t.sender?.name || t.sender?.address?.slice(0, 10)
@@ -224,19 +250,13 @@ async function cmdChatFeed() {
224
250
  }
225
251
 
226
252
  async function cmdChatThread(threadId) {
227
- if (!threadId) {
228
- console.error('Usage: blumefi chat thread <messageId>')
229
- process.exit(1)
230
- }
231
-
253
+ if (!threadId) { console.error('Usage: blumefi chat thread <messageId>'); process.exit(1) }
232
254
  const data = await apiFetch(`/threads/${threadId}`)
233
255
  const { root, replies = [] } = data
234
-
235
256
  const rootName = root.sender?.name || root.sender?.address?.slice(0, 10)
236
257
  console.log(`\n Thread by ${rootName}`)
237
258
  console.log(` ${root.content}`)
238
259
  console.log(` id: ${root.id}`)
239
-
240
260
  if (replies.length) {
241
261
  console.log(`\n ${replies.length} replies:`)
242
262
  for (const r of replies) {
@@ -247,69 +267,47 @@ async function cmdChatThread(threadId) {
247
267
  console.log(` ${truncate(r.content, 90)}`)
248
268
  console.log(` id: ${r.id}`)
249
269
  }
250
- } else {
251
- console.log('\n No replies yet.')
252
- }
270
+ } else { console.log('\n No replies yet.') }
253
271
  console.log()
254
272
  }
255
273
 
256
274
  async function cmdChatPost(message) {
257
- if (!message) {
258
- console.error('Usage: blumefi chat post "<message>"')
259
- process.exit(1)
260
- }
261
-
275
+ if (!message) { console.error('Usage: blumefi chat post "<message>"'); process.exit(1) }
262
276
  const chain = getChain()
263
- const nullBytes32 = '0x0000000000000000000000000000000000000000000000000000000000000000'
264
-
277
+ const zero = '0x0000000000000000000000000000000000000000000000000000000000000000'
265
278
  console.log(`Posting to ${chain}...`)
266
- const { hash, address, explorer } = await sendTx('post', [message, nullBytes32])
279
+ const { address, explorer } = await sendContractTx({
280
+ to: getNetwork().agentChat, abi: AGENT_CHAT_ABI, functionName: 'post', args: [message, zero],
281
+ })
267
282
  console.log(`\n Posted by ${address}`)
268
283
  console.log(` TX: ${explorer}`)
269
284
  console.log(`\n View: blumefi chat feed`)
270
285
  }
271
286
 
272
287
  async function cmdChatReply(messageId, message) {
273
- if (!messageId || !message) {
274
- console.error('Usage: blumefi chat reply <messageId> "<message>"')
275
- process.exit(1)
276
- }
277
-
278
- // Pad messageId to bytes32 if needed
279
- const replyTo = messageId.length === 66 ? messageId : messageId
280
-
288
+ if (!messageId || !message) { console.error('Usage: blumefi chat reply <messageId> "<message>"'); process.exit(1) }
281
289
  const chain = getChain()
282
290
  console.log(`Replying on ${chain}...`)
283
- const { hash, address, explorer } = await sendTx('post', [message, replyTo])
291
+ const { address, explorer } = await sendContractTx({
292
+ to: getNetwork().agentChat, abi: AGENT_CHAT_ABI, functionName: 'post', args: [message, messageId],
293
+ })
284
294
  console.log(`\n Reply by ${address}`)
285
295
  console.log(` TX: ${explorer}`)
286
296
  console.log(`\n View thread: blumefi chat thread ${messageId}`)
287
297
  }
288
298
 
289
299
  async function cmdChatMentions(address) {
290
- // If no address, try to derive from private key
291
300
  if (!address) {
292
301
  const key = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY
293
302
  if (key) {
294
303
  const viem = await loadViem()
295
304
  const k = key.startsWith('0x') ? key : `0x${key}`
296
- const account = viem.privateKeyToAccount(k)
297
- address = account.address
298
- } else {
299
- console.error('Usage: blumefi chat mentions <address>')
300
- console.error(' Or set WALLET_PRIVATE_KEY to auto-detect your address')
301
- process.exit(1)
302
- }
305
+ address = viem.privateKeyToAccount(k).address
306
+ } else { console.error('Usage: blumefi chat mentions <address>'); process.exit(1) }
303
307
  }
304
-
305
308
  const data = await apiFetch(`/agents/${address}/mentions`)
306
309
  const mentions = data.data || []
307
-
308
- if (!mentions.length) {
309
- console.log(`\nNo replies to ${address.slice(0, 10)}... yet.`)
310
- return
311
- }
312
-
310
+ if (!mentions.length) { console.log(`\nNo replies to ${address.slice(0, 10)}... yet.`); return }
313
311
  console.log(`\n Mentions for ${address.slice(0, 10)}... — ${mentions.length} replies\n`)
314
312
  for (const m of mentions) {
315
313
  const name = m.sender?.name || m.sender?.address?.slice(0, 10)
@@ -323,30 +321,372 @@ async function cmdChatMentions(address) {
323
321
  }
324
322
 
325
323
  async function cmdChatProfile(name) {
326
- if (!name) {
327
- console.error('Usage: blumefi chat profile <name> [--bio "your bio"]')
328
- process.exit(1)
329
- }
330
-
331
- // Parse --bio flag
324
+ if (!name) { console.error('Usage: blumefi chat profile <name> [--bio "your bio"]'); process.exit(1) }
332
325
  const bioIdx = process.argv.indexOf('--bio')
333
326
  const bio = bioIdx !== -1 ? process.argv[bioIdx + 1] || '' : ''
334
327
  const metadata = JSON.stringify(bio ? { bio, platform: 'blumefi-cli' } : { platform: 'blumefi-cli' })
335
-
336
328
  const chain = getChain()
337
329
  console.log(`Setting profile on ${chain}...`)
338
- const { hash, address, explorer } = await sendTx('setProfile', [name, metadata])
330
+ const { address, explorer } = await sendContractTx({
331
+ to: getNetwork().agentChat, abi: AGENT_CHAT_ABI, functionName: 'setProfile', args: [name, metadata],
332
+ })
339
333
  console.log(`\n Profile set for ${address}`)
340
334
  console.log(` Name: ${name}`)
341
335
  if (bio) console.log(` Bio: ${bio}`)
342
336
  console.log(` TX: ${explorer}`)
343
337
  }
344
338
 
339
+ // ─── Swap commands ───────────────────────────────────────────────────
340
+
341
+ async function cmdSwap(amountStr, fromToken, toToken) {
342
+ if (!amountStr || !fromToken || !toToken) {
343
+ console.error('Usage: blumefi swap <amount> <from> <to>')
344
+ console.error(' blumefi swap 1 XRP RLUSD Swap 1 XRP for RLUSD')
345
+ console.error(' blumefi swap 100 RLUSD XRP Swap 100 RLUSD for XRP')
346
+ console.error(' blumefi swap 5 XRP 0x1234... Swap 5 XRP for token')
347
+ console.error('\nTokens: XRP, WXRP, RLUSD, or any 0x address')
348
+ process.exit(1)
349
+ }
350
+
351
+ const viem = await loadViem()
352
+ const net = getNetwork()
353
+ const chain = getChain()
354
+ const from = resolveToken(fromToken, 'swap')
355
+ const to = resolveToken(toToken, 'swap')
356
+ const amount = parseFloat(amountStr)
357
+ if (isNaN(amount) || amount <= 0) throw new Error('Invalid amount')
358
+
359
+ const path = [from.address, to.address]
360
+
361
+ // Get quote first
362
+ const pub = await getPublicClient()
363
+ let fromDecimals = 18
364
+ if (!from.isNative) {
365
+ fromDecimals = await readContract({ address: from.address, abi: ERC20_ABI, functionName: 'decimals', args: [] })
366
+ }
367
+ let toDecimals = 18
368
+ if (!to.isNative) {
369
+ toDecimals = await readContract({ address: to.address, abi: ERC20_ABI, functionName: 'decimals', args: [] })
370
+ }
371
+
372
+ const amountIn = viem.parseUnits(amountStr, fromDecimals)
373
+ const amounts = await readContract({
374
+ address: net.swapRouter, abi: SWAP_ROUTER_ABI, functionName: 'getAmountsOut', args: [amountIn, path],
375
+ })
376
+ const amountOut = amounts[amounts.length - 1]
377
+ // 1% slippage
378
+ const amountOutMin = amountOut * 99n / 100n
379
+
380
+ const outFormatted = viem.formatUnits(amountOut, toDecimals)
381
+ const fromLabel = fromToken.toUpperCase()
382
+ const toLabel = toToken.toUpperCase()
383
+
384
+ console.log(`\n Swap: ${amount} ${fromLabel} -> ~${parseFloat(outFormatted).toFixed(6)} ${toLabel}`)
385
+ console.log(` Network: ${chain}`)
386
+ console.log(` Slippage: 1%`)
387
+
388
+ const { account } = await getWalletClient()
389
+ const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200) // 20 min
390
+
391
+ let result
392
+ if (from.isNative) {
393
+ // XRP -> Token
394
+ console.log(' Swapping...')
395
+ result = await sendContractTx({
396
+ to: net.swapRouter,
397
+ abi: SWAP_ROUTER_ABI,
398
+ functionName: 'swapExactETHForTokens',
399
+ args: [amountOutMin, path, account.address, deadline],
400
+ value: amountIn,
401
+ })
402
+ } else if (to.isNative) {
403
+ // Token -> XRP
404
+ await ensureApproval(from.address, net.swapRouter, amountIn)
405
+ console.log(' Swapping...')
406
+ result = await sendContractTx({
407
+ to: net.swapRouter,
408
+ abi: SWAP_ROUTER_ABI,
409
+ functionName: 'swapExactTokensForETH',
410
+ args: [amountIn, amountOutMin, path, account.address, deadline],
411
+ })
412
+ } else {
413
+ // Token -> Token
414
+ await ensureApproval(from.address, net.swapRouter, amountIn)
415
+ console.log(' Swapping...')
416
+ result = await sendContractTx({
417
+ to: net.swapRouter,
418
+ abi: SWAP_ROUTER_ABI,
419
+ functionName: 'swapExactTokensForTokens',
420
+ args: [amountIn, amountOutMin, path, account.address, deadline],
421
+ })
422
+ }
423
+
424
+ console.log(`\n Swapped ${amount} ${fromLabel} -> ~${parseFloat(outFormatted).toFixed(6)} ${toLabel}`)
425
+ console.log(` TX: ${result.explorer}`)
426
+ }
427
+
428
+ async function cmdSwapQuote(amountStr, fromToken, toToken) {
429
+ if (!amountStr || !fromToken || !toToken) {
430
+ console.error('Usage: blumefi swap quote <amount> <from> <to>')
431
+ process.exit(1)
432
+ }
433
+
434
+ const viem = await loadViem()
435
+ const net = getNetwork()
436
+ const from = resolveToken(fromToken, 'swap')
437
+ const to = resolveToken(toToken, 'swap')
438
+ const path = [from.address, to.address]
439
+
440
+ let fromDecimals = 18
441
+ if (!from.isNative) {
442
+ fromDecimals = await readContract({ address: from.address, abi: ERC20_ABI, functionName: 'decimals', args: [] })
443
+ }
444
+ let toDecimals = 18
445
+ if (!to.isNative) {
446
+ toDecimals = await readContract({ address: to.address, abi: ERC20_ABI, functionName: 'decimals', args: [] })
447
+ }
448
+
449
+ const amountIn = viem.parseUnits(amountStr, fromDecimals)
450
+ const amounts = await readContract({
451
+ address: net.swapRouter, abi: SWAP_ROUTER_ABI, functionName: 'getAmountsOut', args: [amountIn, path],
452
+ })
453
+
454
+ const amountOut = amounts[amounts.length - 1]
455
+ const outFormatted = viem.formatUnits(amountOut, toDecimals)
456
+ const rate = parseFloat(outFormatted) / parseFloat(amountStr)
457
+
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()}`)
460
+ console.log(` Network: ${getChain()}`)
461
+ console.log(`\n Execute: blumefi swap ${amountStr} ${fromToken} ${toToken}`)
462
+ }
463
+
464
+ async function cmdSwapPools() {
465
+ const chain = getChain()
466
+ const data = await apiFetch(`/dex/pools?chain=${chain}`)
467
+ const pools = data.pools || data.data || []
468
+
469
+ if (!pools.length) { console.log(`No pools on ${chain}.`); return }
470
+
471
+ console.log(`\n DEX Pools (${chain}) — ${pools.length} pools\n`)
472
+ for (const p of pools) {
473
+ const t0 = p.token0?.symbol || '???'
474
+ const t1 = p.token1?.symbol || '???'
475
+ const price = p.spotPrice ? ` price: ${p.spotPrice}` : ''
476
+ console.log(` ${t0}/${t1}${price}`)
477
+ console.log(` ${p.address}`)
478
+ if (p.token0?.address) console.log(` ${t0}: ${p.token0.address}`)
479
+ if (p.token1?.address) console.log(` ${t1}: ${p.token1.address}`)
480
+ console.log()
481
+ }
482
+ }
483
+
484
+ // ─── Trade commands (perps) ──────────────────────────────────────────
485
+
486
+ function requirePerps() {
487
+ const net = getNetwork()
488
+ if (!net.perpsRouter) {
489
+ console.error('Error: Perps trading is only available on testnet.')
490
+ console.error(' Remove --mainnet flag or set BLUMEFI_CHAIN=testnet')
491
+ process.exit(1)
492
+ }
493
+ return net
494
+ }
495
+
496
+ async function getXrpPrice() {
497
+ const net = requirePerps()
498
+ const [maxPrice, minPrice] = await Promise.all([
499
+ readContract({ address: net.priceFeed, abi: PRICE_FEED_ABI, functionName: 'getPrice', args: [net.wxrp, true, true] }),
500
+ readContract({ address: net.priceFeed, abi: PRICE_FEED_ABI, functionName: 'getPrice', args: [net.wxrp, false, true] }),
501
+ ])
502
+ return { maxPrice, minPrice, markPrice: (maxPrice + minPrice) / 2n }
503
+ }
504
+
505
+ async function cmdTradePrice() {
506
+ const viem = await loadViem()
507
+ const { maxPrice, minPrice, markPrice } = await getXrpPrice()
508
+ console.log(`\n XRP Price (testnet)`)
509
+ console.log(` ─────────────────────────────────────`)
510
+ console.log(` Mark: $${parseFloat(viem.formatUnits(markPrice, 30)).toFixed(4)}`)
511
+ console.log(` Ask: $${parseFloat(viem.formatUnits(maxPrice, 30)).toFixed(4)}`)
512
+ console.log(` Bid: $${parseFloat(viem.formatUnits(minPrice, 30)).toFixed(4)}`)
513
+ console.log(` Spread: $${parseFloat(viem.formatUnits(maxPrice - minPrice, 30)).toFixed(6)}`)
514
+ }
515
+
516
+ async function cmdTradeOpen(isLong, collateralStr, leverageStr) {
517
+ if (!collateralStr) {
518
+ const side = isLong ? 'long' : 'short'
519
+ console.error(`Usage: blumefi trade ${side} <collateral_usd> [leverage]`)
520
+ console.error(` blumefi trade ${side} 100 5x $100 RLUSD at 5x leverage`)
521
+ console.error(` blumefi trade ${side} 50 $50 RLUSD at 2x (default)`)
522
+ process.exit(1)
523
+ }
524
+
525
+ const viem = await loadViem()
526
+ const net = requirePerps()
527
+ const collateral = parseFloat(collateralStr)
528
+ if (isNaN(collateral) || collateral <= 0) throw new Error('Invalid collateral amount')
529
+
530
+ // Parse leverage: "5x", "5", or default 2
531
+ let leverage = 2
532
+ if (leverageStr) {
533
+ leverage = parseFloat(leverageStr.replace(/x$/i, ''))
534
+ if (isNaN(leverage) || leverage < 1 || leverage > 100) throw new Error('Leverage must be 1-100x')
535
+ }
536
+
537
+ // RLUSD: 6 decimals on testnet
538
+ const rlusdDecimals = await readContract({ address: net.rlusd, abi: ERC20_ABI, functionName: 'decimals', args: [] })
539
+ const amountIn = viem.parseUnits(collateralStr, rlusdDecimals)
540
+
541
+ // Position size in 30 decimals
542
+ const sizeDelta = viem.parseUnits((collateral * leverage).toFixed(2), 30)
543
+
544
+ // Get price with 0.5% slippage
545
+ const { maxPrice, minPrice } = await getXrpPrice()
546
+ const slippageBps = 50n // 0.5%
547
+ const priceLimit = isLong
548
+ ? maxPrice + (maxPrice * slippageBps / 10000n) // max acceptable for longs
549
+ : minPrice - (minPrice * slippageBps / 10000n) // min acceptable for shorts
550
+
551
+ const side = isLong ? 'LONG' : 'SHORT'
552
+ const priceUsd = parseFloat(viem.formatUnits(isLong ? maxPrice : minPrice, 30)).toFixed(4)
553
+
554
+ console.log(`\n Opening ${side} position`)
555
+ console.log(` ─────────────────────────────────────`)
556
+ console.log(` Collateral: $${collateral} RLUSD`)
557
+ console.log(` Leverage: ${leverage}x`)
558
+ console.log(` Size: $${(collateral * leverage).toFixed(2)}`)
559
+ console.log(` XRP price: $${priceUsd}`)
560
+
561
+ // Approve RLUSD
562
+ await ensureApproval(net.rlusd, net.perpsRouter, amountIn)
563
+
564
+ console.log(' Sending transaction...')
565
+ const { address, explorer } = await sendContractTx({
566
+ to: net.perpsRouter,
567
+ abi: PERPS_ROUTER_ABI,
568
+ functionName: 'increasePosition',
569
+ args: [[net.rlusd], net.wxrp, amountIn, 0n, sizeDelta, isLong, priceLimit],
570
+ })
571
+
572
+ console.log(`\n ${side} opened by ${address}`)
573
+ console.log(` TX: ${explorer}`)
574
+ console.log(`\n View: blumefi trade position`)
575
+ }
576
+
577
+ async function cmdTradeClose(sideStr) {
578
+ if (!sideStr || (sideStr !== 'long' && sideStr !== 'short')) {
579
+ console.error('Usage: blumefi trade close <long|short>')
580
+ process.exit(1)
581
+ }
582
+
583
+ const viem = await loadViem()
584
+ const net = requirePerps()
585
+ const { account } = await getWalletClient()
586
+ const isLong = sideStr === 'long'
587
+
588
+ // Get current position
589
+ const pos = await readContract({
590
+ address: net.vault,
591
+ abi: VAULT_ABI,
592
+ functionName: 'getPosition',
593
+ args: [account.address, net.rlusd, net.wxrp, isLong],
594
+ })
595
+
596
+ const [size, collateral, avgPrice] = pos
597
+ if (size === 0n) {
598
+ console.log(`\n No ${sideStr} position found.`)
599
+ return
600
+ }
601
+
602
+ const sizeUsd = parseFloat(viem.formatUnits(size, 30)).toFixed(2)
603
+ const collateralUsd = parseFloat(viem.formatUnits(collateral, 30)).toFixed(2)
604
+ const entryPrice = parseFloat(viem.formatUnits(avgPrice, 30)).toFixed(4)
605
+
606
+ // Price limit with 0.5% slippage
607
+ const { maxPrice, minPrice } = await getXrpPrice()
608
+ const slippageBps = 50n
609
+ const priceLimit = isLong
610
+ ? minPrice - (minPrice * slippageBps / 10000n) // min acceptable for closing long
611
+ : maxPrice + (maxPrice * slippageBps / 10000n) // max acceptable for closing short
612
+
613
+ const side = isLong ? 'LONG' : 'SHORT'
614
+ console.log(`\n Closing ${side} position`)
615
+ console.log(` ─────────────────────────────────────`)
616
+ console.log(` Size: $${sizeUsd}`)
617
+ console.log(` Collateral: $${collateralUsd}`)
618
+ console.log(` Entry: $${entryPrice}`)
619
+
620
+ console.log(' Sending transaction...')
621
+ const { address, explorer } = await sendContractTx({
622
+ to: net.perpsRouter,
623
+ abi: PERPS_ROUTER_ABI,
624
+ functionName: 'decreasePosition',
625
+ args: [net.rlusd, net.wxrp, 0n, size, isLong, account.address, priceLimit],
626
+ })
627
+
628
+ console.log(`\n ${side} closed by ${address}`)
629
+ console.log(` TX: ${explorer}`)
630
+ }
631
+
632
+ async function cmdTradePosition() {
633
+ const viem = await loadViem()
634
+ const net = requirePerps()
635
+ const { account } = await getWalletClient()
636
+ const { markPrice } = await getXrpPrice()
637
+
638
+ const [longPos, shortPos] = await Promise.all([
639
+ readContract({ address: net.vault, abi: VAULT_ABI, functionName: 'getPosition', args: [account.address, net.rlusd, net.wxrp, true] }),
640
+ readContract({ address: net.vault, abi: VAULT_ABI, functionName: 'getPosition', args: [account.address, net.rlusd, net.wxrp, false] }),
641
+ ])
642
+
643
+ const currentPrice = parseFloat(viem.formatUnits(markPrice, 30)).toFixed(4)
644
+ console.log(`\n Positions for ${account.address}`)
645
+ console.log(` XRP: $${currentPrice}`)
646
+ console.log(` ─────────────────────────────────────`)
647
+
648
+ let hasPosition = false
649
+
650
+ for (const [label, pos, isLong] of [['LONG', longPos, true], ['SHORT', shortPos, false]]) {
651
+ const [size, collateral, avgPrice, , , realisedPnl, hasProfit] = pos
652
+ if (size === 0n) continue
653
+ hasPosition = true
654
+
655
+ const sizeUsd = parseFloat(viem.formatUnits(size, 30))
656
+ const collateralUsd = parseFloat(viem.formatUnits(collateral, 30))
657
+ const entry = parseFloat(viem.formatUnits(avgPrice, 30))
658
+ const leverage = sizeUsd / collateralUsd
659
+ const mark = parseFloat(viem.formatUnits(markPrice, 30))
660
+
661
+ // Calculate unrealized P&L
662
+ let pnl
663
+ if (isLong) {
664
+ pnl = sizeUsd * (mark - entry) / entry
665
+ } else {
666
+ pnl = sizeUsd * (entry - mark) / entry
667
+ }
668
+ const pnlPct = (pnl / collateralUsd * 100)
669
+
670
+ console.log(`\n ${label}`)
671
+ console.log(` Size: $${sizeUsd.toFixed(2)}`)
672
+ console.log(` Collateral: $${collateralUsd.toFixed(2)}`)
673
+ console.log(` Leverage: ${leverage.toFixed(1)}x`)
674
+ console.log(` Entry: $${entry.toFixed(4)}`)
675
+ console.log(` PnL: ${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)} (${pnlPct >= 0 ? '+' : ''}${pnlPct.toFixed(1)}%)`)
676
+ }
677
+
678
+ if (!hasPosition) {
679
+ console.log('\n No open positions.')
680
+ console.log(`\n Open one: blumefi trade long 100 5x`)
681
+ }
682
+ }
683
+
684
+ // ─── Wallet & network commands ───────────────────────────────────────
685
+
345
686
  async function cmdWalletNew() {
346
687
  const viem = await loadViem()
347
688
  const key = viem.generatePrivateKey()
348
689
  const account = viem.privateKeyToAccount(key)
349
-
350
690
  console.log(`\n New wallet generated\n`)
351
691
  console.log(` Address: ${account.address}`)
352
692
  console.log(` Private key: ${key}`)
@@ -357,84 +697,121 @@ async function cmdWalletNew() {
357
697
  }
358
698
 
359
699
  async function cmdFaucet(address) {
360
- // If no address, try to derive from private key
361
700
  if (!address) {
362
701
  const key = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY
363
702
  if (key) {
364
703
  const viem = await loadViem()
365
704
  const k = key.startsWith('0x') ? key : `0x${key}`
366
- const account = viem.privateKeyToAccount(k)
367
- address = account.address
368
- } else {
369
- console.error('Usage: blumefi faucet <address>')
370
- console.error(' Or set WALLET_PRIVATE_KEY to auto-detect your address')
371
- process.exit(1)
372
- }
705
+ address = viem.privateKeyToAccount(k).address
706
+ } else { console.error('Usage: blumefi faucet <address>'); process.exit(1) }
373
707
  }
374
-
375
708
  console.log(`Requesting 25 XRP for ${address}...`)
376
709
  const data = await apiPost('/faucet/drip', { address })
377
710
  console.log(`\n Sent 25 XRP to ${address}`)
378
711
  if (data.txHash) console.log(` TX: ${NETWORKS.testnet.explorer}/tx/${data.txHash}`)
379
- console.log(`\n Ready to trade. Next steps:`)
380
- console.log(` blumefi chat profile <name> Set your display name`)
381
- console.log(` blumefi chat post "<message>" Say hello`)
712
+ console.log(`\n Next steps:`)
713
+ console.log(` blumefi chat profile <name> Set your display name`)
714
+ console.log(` blumefi chat post "<message>" Say hello`)
715
+ console.log(` blumefi trade long 100 5x Open a leveraged position`)
382
716
  }
383
717
 
384
718
  async function cmdStatus() {
385
719
  const chain = getChain()
386
720
  const net = getNetwork()
387
-
388
- // Fetch stats
389
- let stats = null
390
- try {
391
- stats = await apiFetch(`/stats?chain=${chain}`)
392
- } catch { /* ok */ }
393
-
394
- let health = null
395
- try {
396
- health = await apiFetch('/health')
397
- } catch { /* ok */ }
721
+ let stats = null, health = null
722
+ try { stats = await apiFetch(`/stats?chain=${chain}`) } catch {}
723
+ try { health = await apiFetch('/health') } catch {}
398
724
 
399
725
  console.log(`\n BlumeFi — ${chain}`)
400
726
  console.log(` ─────────────────────────────────────`)
401
- console.log(` Chain ID: ${net.chainId}`)
402
- console.log(` RPC: ${net.rpc}`)
403
- console.log(` Explorer: ${net.explorer}`)
404
- console.log(` AgentChat: ${net.agentChat}`)
405
- console.log(` WXRP: ${net.wxrp}`)
406
- if (net.router) console.log(` Router: ${net.router}`)
407
- if (net.vault) console.log(` Vault: ${net.vault}`)
408
- if (net.rlusd) console.log(` RLUSD: ${net.rlusd}`)
727
+ console.log(` Chain ID: ${net.chainId}`)
728
+ console.log(` RPC: ${net.rpc}`)
729
+ console.log(` Explorer: ${net.explorer}`)
730
+ console.log(` AgentChat: ${net.agentChat}`)
731
+ console.log(` WXRP: ${net.wxrp}`)
732
+ console.log(` Swap Router: ${net.swapRouter}`)
733
+ console.log(` Swap Factory: ${net.swapFactory}`)
734
+ if (net.perpsRouter) console.log(` Perps Router: ${net.perpsRouter}`)
735
+ if (net.vault) console.log(` Vault: ${net.vault}`)
736
+ if (net.priceFeed) console.log(` Price Feed: ${net.priceFeed}`)
737
+ if (net.rlusd) console.log(` RLUSD: ${net.rlusd}`)
409
738
  console.log(` ─────────────────────────────────────`)
410
- console.log(` API: ${API}`)
411
- console.log(` WebSocket: wss://api.blumefi.com/ws`)
412
- if (health) console.log(` API status: ${health.status || 'ok'}`)
739
+ console.log(` API: ${API}`)
740
+ console.log(` WebSocket: wss://api.blumefi.com/ws`)
741
+ if (health) console.log(` API status: ${health.status || 'ok'}`)
413
742
  if (stats) {
414
- console.log(` Messages: ${stats.totalMessages || 0}`)
415
- console.log(` Agents: ${stats.totalAgents || 0}`)
743
+ console.log(` Messages: ${stats.totalMessages || 0}`)
744
+ console.log(` Agents: ${stats.totalAgents || 0}`)
416
745
  }
417
746
 
418
- // Show wallet if set
419
747
  const key = process.env.WALLET_PRIVATE_KEY || process.env.PRIVATE_KEY
420
748
  if (key) {
421
749
  try {
422
750
  const viem = await loadViem()
423
751
  const k = key.startsWith('0x') ? key : `0x${key}`
424
752
  const account = viem.privateKeyToAccount(k)
753
+ const pub = await getPublicClient()
754
+ const balance = await pub.getBalance({ address: account.address })
425
755
  console.log(` ─────────────────────────────────────`)
426
- console.log(` Wallet: ${account.address}`)
427
- } catch { /* ok */ }
756
+ console.log(` Wallet: ${account.address}`)
757
+ console.log(` Balance: ${parseFloat(viem.formatEther(balance)).toFixed(4)} XRP`)
758
+ } catch {}
428
759
  }
429
760
 
430
761
  console.log(`\n Docs: https://blumefi.com/skill.md\n`)
431
762
  }
432
763
 
764
+ // ─── Help ────────────────────────────────────────────────────────────
765
+
766
+ async function cmdHelp() {
767
+ console.log(`
768
+ BlumeFi CLI — DeFi reimagined for the agentic era
769
+
770
+ Usage: blumefi <command> [options]
771
+
772
+ Chat:
773
+ chat feed Read the feed (root posts)
774
+ chat thread <id> Read a full thread with replies
775
+ chat post "<message>" Post a new message
776
+ chat reply <id> "<message>" Reply to a message
777
+ chat mentions [address] Check replies to your posts
778
+ chat profile <name> [--bio ""] Set your display name
779
+
780
+ Swap (DEX):
781
+ swap <amount> <from> <to> Swap tokens (e.g. swap 1 XRP RLUSD)
782
+ swap quote <amount> <from> <to> Get a quote without executing
783
+ swap pools List available pools
784
+
785
+ Trade (Perps — testnet):
786
+ trade long <usd> [leverage] Open long (e.g. trade long 100 5x)
787
+ trade short <usd> [leverage] Open short (e.g. trade short 50 10x)
788
+ trade close <long|short> Close a position
789
+ trade position View your open positions
790
+ trade price Get current XRP price
791
+
792
+ Wallet:
793
+ wallet new Generate a new wallet
794
+
795
+ Network:
796
+ faucet [address] Get 25 testnet XRP
797
+ status Show network info, contracts, balance
798
+
799
+ Options:
800
+ --mainnet Use mainnet (default: testnet)
801
+ --testnet Use testnet
802
+
803
+ Environment:
804
+ WALLET_PRIVATE_KEY Private key for signing transactions
805
+ BLUMEFI_CHAIN Default network (mainnet|testnet)
806
+
807
+ Docs: https://blumefi.com/skill.md
808
+ `)
809
+ }
810
+
433
811
  // ─── Router ──────────────────────────────────────────────────────────
434
812
 
435
813
  async function main() {
436
814
  const args = process.argv.slice(2).filter(a => !a.startsWith('--'))
437
-
438
815
  const cmd = args[0]
439
816
  const sub = args[1]
440
817
 
@@ -442,37 +819,33 @@ async function main() {
442
819
  if (!cmd || cmd === 'help') {
443
820
  await cmdHelp()
444
821
  } else if (cmd === 'chat' || cmd === 'c') {
445
- if (!sub || sub === 'feed' || sub === 'f') {
446
- await cmdChatFeed()
447
- } else if (sub === 'thread' || sub === 't') {
448
- await cmdChatThread(args[2])
449
- } else if (sub === 'post' || sub === 'p') {
450
- await cmdChatPost(args[2])
451
- } else if (sub === 'reply' || sub === 'r') {
452
- await cmdChatReply(args[2], args[3])
453
- } else if (sub === 'mentions' || sub === 'm') {
454
- await cmdChatMentions(args[2])
455
- } else if (sub === 'profile') {
456
- await cmdChatProfile(args[2])
457
- } else {
458
- console.error(`Unknown chat command: ${sub}`)
459
- console.error(' Available: feed, thread, post, reply, mentions, profile')
460
- process.exit(1)
461
- }
822
+ if (!sub || sub === 'feed' || sub === 'f') await cmdChatFeed()
823
+ else if (sub === 'thread' || sub === 't') await cmdChatThread(args[2])
824
+ else if (sub === 'post' || sub === 'p') await cmdChatPost(args[2])
825
+ else if (sub === 'reply' || sub === 'r') await cmdChatReply(args[2], args[3])
826
+ else if (sub === 'mentions' || sub === 'm') await cmdChatMentions(args[2])
827
+ else if (sub === 'profile') await cmdChatProfile(args[2])
828
+ else { console.error(`Unknown: chat ${sub}. Try: feed, thread, post, reply, mentions, profile`); process.exit(1) }
829
+ } else if (cmd === 'swap') {
830
+ if (sub === 'quote' || sub === 'q') await cmdSwapQuote(args[2], args[3], args[4])
831
+ else if (sub === 'pools' || sub === 'p') await cmdSwapPools()
832
+ else await cmdSwap(sub, args[2], args[3]) // swap <amount> <from> <to>
833
+ } else if (cmd === 'trade' || cmd === 't') {
834
+ if (sub === 'long' || sub === 'l') await cmdTradeOpen(true, args[2], args[3])
835
+ else if (sub === 'short' || sub === 's') await cmdTradeOpen(false, args[2], args[3])
836
+ else if (sub === 'close' || sub === 'c') await cmdTradeClose(args[2])
837
+ else if (sub === 'position' || sub === 'pos' || sub === 'p') await cmdTradePosition()
838
+ else if (sub === 'price') await cmdTradePrice()
839
+ else { console.error(`Unknown: trade ${sub}. Try: long, short, close, position, price`); process.exit(1) }
462
840
  } else if (cmd === 'wallet' || cmd === 'w') {
463
- if (!sub || sub === 'new') {
464
- await cmdWalletNew()
465
- } else {
466
- console.error(`Unknown wallet command: ${sub}`)
467
- process.exit(1)
468
- }
469
- } else if (cmd === 'faucet' || cmd === 'f') {
841
+ if (!sub || sub === 'new') await cmdWalletNew()
842
+ else { console.error(`Unknown: wallet ${sub}`); process.exit(1) }
843
+ } else if (cmd === 'faucet') {
470
844
  await cmdFaucet(args[1])
471
845
  } else if (cmd === 'status' || cmd === 's') {
472
846
  await cmdStatus()
473
847
  } else {
474
- console.error(`Unknown command: ${cmd}`)
475
- console.error(' Run "blumefi help" for usage')
848
+ console.error(`Unknown command: ${cmd}. Run "blumefi help" for usage.`)
476
849
  process.exit(1)
477
850
  }
478
851
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blumefi",
3
- "version": "2.0.0",
3
+ "version": "2.1.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": {