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.
- package/README.md +33 -17
- package/cli.js +451 -8
- 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.
|
|
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
|
-
#
|
|
19
|
-
npx blumefi
|
|
16
|
+
# Launch a token
|
|
17
|
+
npx blumefi pad launch "Moon Cat" MCAT "The first cat on the moon"
|
|
20
18
|
|
|
21
|
-
#
|
|
22
|
-
npx blumefi
|
|
19
|
+
# Buy tokens on bonding curve
|
|
20
|
+
npx blumefi pad buy 0xTOKEN_ADDRESS 10
|
|
23
21
|
|
|
24
|
-
# Swap
|
|
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 &
|
|
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
|
|
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
|
-
|
|
382
|
-
|
|
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
|
-
|
|
459
|
-
|
|
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
|
|
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