blumefi 2.6.1 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +212 -2
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -62,7 +62,7 @@ const NETWORKS = {
|
|
|
62
62
|
wxrp: '0x7C21a90E3eCD3215d16c3BBe76a491f8f792d4Bf',
|
|
63
63
|
swapRouter: '0x3a5FF5717fCa60b613B28610A8Fd2E13299e306C',
|
|
64
64
|
swapFactory: '0x0F0F367e1C407C28821899E9bd2CB63D6086a945',
|
|
65
|
-
padFactory: '
|
|
65
|
+
padFactory: '0x8681A2566E35b14196cAc0E349DB585992d313BA',
|
|
66
66
|
},
|
|
67
67
|
testnet: {
|
|
68
68
|
chainId: 1449000,
|
|
@@ -73,7 +73,7 @@ const NETWORKS = {
|
|
|
73
73
|
swapWxrp: '0x664950b1F3E2FAF98286571381f5f4c230ffA9c5', // Swap router uses different WXRP on testnet
|
|
74
74
|
swapRouter: '0xC17E3517131E7444361fEA2083F3309B33a7320A',
|
|
75
75
|
swapFactory: '0xa67Dfa5C47Bec4bBbb06794B933705ADb9E82459',
|
|
76
|
-
padFactory: '
|
|
76
|
+
padFactory: '0xe7a942B532333761641f3326a4583956C557Bee2',
|
|
77
77
|
perpsRouter: '0x2eDAa73b84Fcc8B403FC4fa10B15458B07560422',
|
|
78
78
|
vault: '0x013C9b57169587c374de63A63DC92bfbc744ef4a',
|
|
79
79
|
priceFeed: '0xBbB98D02Dc2e218e8f864E3667AA699557b62aF9',
|
|
@@ -102,6 +102,22 @@ const SWAP_ROUTER_ABI = [
|
|
|
102
102
|
{ name: 'swapExactTokensForTokens', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'amountOutMin', type: 'uint256' }, { name: 'path', type: 'address[]' }, { name: 'to', type: 'address' }, { name: 'deadline', type: 'uint256' }], outputs: [{ type: 'uint256[]' }] },
|
|
103
103
|
{ name: 'getAmountsOut', type: 'function', stateMutability: 'view', inputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'path', type: 'address[]' }], outputs: [{ type: 'uint256[]' }] },
|
|
104
104
|
{ name: 'WETH', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
105
|
+
{ name: 'addLiquidityETH', type: 'function', stateMutability: 'payable', inputs: [{ name: 'token', type: 'address' }, { name: 'amountTokenDesired', type: 'uint256' }, { name: 'amountTokenMin', type: 'uint256' }, { name: 'amountETHMin', type: 'uint256' }, { name: 'to', type: 'address' }, { name: 'deadline', type: 'uint256' }], outputs: [{ name: 'amountToken', type: 'uint256' }, { name: 'amountETH', type: 'uint256' }, { name: 'liquidity', type: 'uint256' }] },
|
|
106
|
+
{ name: 'removeLiquidityETH', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'token', type: 'address' }, { name: 'liquidity', type: 'uint256' }, { name: 'amountTokenMin', type: 'uint256' }, { name: 'amountETHMin', type: 'uint256' }, { name: 'to', type: 'address' }, { name: 'deadline', type: 'uint256' }], outputs: [{ name: 'amountToken', type: 'uint256' }, { name: 'amountETH', type: 'uint256' }] },
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
const SWAP_FACTORY_ABI = [
|
|
110
|
+
{ name: 'getPair', type: 'function', stateMutability: 'view', inputs: [{ name: 'tokenA', type: 'address' }, { name: 'tokenB', type: 'address' }], outputs: [{ type: 'address' }] },
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
const SWAP_PAIR_ABI = [
|
|
114
|
+
{ name: 'getReserves', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: 'reserve0', type: 'uint112' }, { name: 'reserve1', type: 'uint112' }, { name: 'blockTimestampLast', type: 'uint32' }] },
|
|
115
|
+
{ name: 'token0', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
116
|
+
{ name: 'token1', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
|
|
117
|
+
{ name: 'totalSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
|
|
118
|
+
{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
119
|
+
{ name: 'approve', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
|
|
120
|
+
{ name: 'allowance', type: 'function', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
105
121
|
]
|
|
106
122
|
|
|
107
123
|
const PERPS_ROUTER_ABI = [
|
|
@@ -585,6 +601,196 @@ async function cmdSwapPools() {
|
|
|
585
601
|
}
|
|
586
602
|
}
|
|
587
603
|
|
|
604
|
+
async function cmdSwapAddLiquidity(tokenAddress, xrpAmountStr) {
|
|
605
|
+
if (!tokenAddress || !xrpAmountStr) {
|
|
606
|
+
console.error('Usage: blumefi swap add-liquidity <token_address> <xrp_amount>')
|
|
607
|
+
console.error(' blumefi swap add-liquidity 0x1234... 10 Add liquidity with 10 XRP')
|
|
608
|
+
console.error(' blumefi swap add-liquidity 0x1234... 10 --slippage 5')
|
|
609
|
+
process.exit(1)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const viem = await loadViem()
|
|
613
|
+
const net = getNetwork()
|
|
614
|
+
const chain = getChain()
|
|
615
|
+
const xrpAmount = parseFloat(xrpAmountStr)
|
|
616
|
+
if (isNaN(xrpAmount) || xrpAmount <= 0) throw new Error('Invalid XRP amount')
|
|
617
|
+
|
|
618
|
+
// Parse --slippage flag (default 2%)
|
|
619
|
+
const slippageIdx = process.argv.indexOf('--slippage')
|
|
620
|
+
const slippagePct = slippageIdx !== -1 ? parseFloat(process.argv[slippageIdx + 1] || '2') : 2
|
|
621
|
+
if (isNaN(slippagePct) || slippagePct <= 0 || slippagePct > 50) throw new Error('Slippage must be between 0 and 50')
|
|
622
|
+
const slippageBps = BigInt(Math.round(slippagePct * 100))
|
|
623
|
+
|
|
624
|
+
const wxrp = net.swapWxrp || net.wxrp
|
|
625
|
+
const xrpValue = viem.parseEther(xrpAmountStr)
|
|
626
|
+
|
|
627
|
+
// Get pair address
|
|
628
|
+
const pairAddress = await readContract({
|
|
629
|
+
address: net.swapFactory, abi: SWAP_FACTORY_ABI, functionName: 'getPair', args: [tokenAddress, wxrp],
|
|
630
|
+
})
|
|
631
|
+
const zeroPair = '0x0000000000000000000000000000000000000000'
|
|
632
|
+
if (!pairAddress || pairAddress === zeroPair) {
|
|
633
|
+
throw new Error(`No pool exists for this token/XRP pair. Create one on swap.blumefi.com first.`)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Get reserves and figure out which side is the token vs WXRP
|
|
637
|
+
const [reserves, token0Addr, tokenDecimals, tokenSymbol] = await Promise.all([
|
|
638
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'getReserves', args: [] }),
|
|
639
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'token0', args: [] }),
|
|
640
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'decimals', args: [] }),
|
|
641
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'symbol', args: [] }),
|
|
642
|
+
])
|
|
643
|
+
|
|
644
|
+
const isToken0 = token0Addr.toLowerCase() === tokenAddress.toLowerCase()
|
|
645
|
+
const tokenReserve = isToken0 ? reserves[0] : reserves[1]
|
|
646
|
+
const xrpReserve = isToken0 ? reserves[1] : reserves[0]
|
|
647
|
+
|
|
648
|
+
if (xrpReserve === 0n || tokenReserve === 0n) throw new Error('Pool has no liquidity')
|
|
649
|
+
|
|
650
|
+
// Calculate required token amount at current pool ratio
|
|
651
|
+
// tokenAmount = xrpAmount * tokenReserve / xrpReserve (+ 1 to round up)
|
|
652
|
+
const tokenAmountRaw = xrpValue * tokenReserve / xrpReserve
|
|
653
|
+
const tokenAmountDesired = tokenAmountRaw + 1n
|
|
654
|
+
|
|
655
|
+
const tokenFormatted = parseFloat(viem.formatUnits(tokenAmountDesired, tokenDecimals)).toLocaleString()
|
|
656
|
+
|
|
657
|
+
// Slippage minimums (from raw ratio, not bumped desired)
|
|
658
|
+
const amountTokenMin = tokenAmountRaw * (10000n - slippageBps) / 10000n
|
|
659
|
+
const amountETHMin = xrpValue * (10000n - slippageBps) / 10000n
|
|
660
|
+
|
|
661
|
+
console.log(`\n Adding liquidity on ${chain}`)
|
|
662
|
+
console.log(` ─────────────────────────────────────`)
|
|
663
|
+
console.log(` Pool: ${tokenSymbol} / XRP`)
|
|
664
|
+
console.log(` XRP: ${xrpAmount} XRP`)
|
|
665
|
+
console.log(` Token: ~${tokenFormatted} ${tokenSymbol}`)
|
|
666
|
+
console.log(` Slippage: ${slippagePct}%`)
|
|
667
|
+
|
|
668
|
+
// Approve token for router
|
|
669
|
+
await ensureApproval(tokenAddress, net.swapRouter, tokenAmountDesired)
|
|
670
|
+
|
|
671
|
+
const { account } = await getWalletClient()
|
|
672
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200) // 20 min
|
|
673
|
+
|
|
674
|
+
console.log(' Sending transaction...')
|
|
675
|
+
const { hash, explorer } = await sendContractTx({
|
|
676
|
+
to: net.swapRouter,
|
|
677
|
+
abi: SWAP_ROUTER_ABI,
|
|
678
|
+
functionName: 'addLiquidityETH',
|
|
679
|
+
args: [tokenAddress, tokenAmountDesired, amountTokenMin, amountETHMin, account.address, deadline],
|
|
680
|
+
value: xrpValue,
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
console.log(`\n Added liquidity: ${xrpAmount} XRP + ~${tokenFormatted} ${tokenSymbol}`)
|
|
684
|
+
console.log(` TX: ${explorer}`)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async function cmdSwapRemoveLiquidity(tokenAddress, lpAmountStr) {
|
|
688
|
+
if (!tokenAddress || !lpAmountStr) {
|
|
689
|
+
console.error('Usage: blumefi swap remove-liquidity <token_address> <lp_amount|all>')
|
|
690
|
+
console.error(' blumefi swap remove-liquidity 0x1234... all Remove all liquidity')
|
|
691
|
+
console.error(' blumefi swap remove-liquidity 0x1234... 5 Remove 5 LP tokens')
|
|
692
|
+
console.error(' blumefi swap remove-liquidity 0x1234... 5 --slippage 5')
|
|
693
|
+
process.exit(1)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const viem = await loadViem()
|
|
697
|
+
const net = getNetwork()
|
|
698
|
+
const chain = getChain()
|
|
699
|
+
const { account } = await getWalletClient()
|
|
700
|
+
|
|
701
|
+
// Parse --slippage flag (default 2%)
|
|
702
|
+
const slippageIdx = process.argv.indexOf('--slippage')
|
|
703
|
+
const slippagePct = slippageIdx !== -1 ? parseFloat(process.argv[slippageIdx + 1] || '2') : 2
|
|
704
|
+
if (isNaN(slippagePct) || slippagePct <= 0 || slippagePct > 50) throw new Error('Slippage must be between 0 and 50')
|
|
705
|
+
const slippageBps = BigInt(Math.round(slippagePct * 100))
|
|
706
|
+
|
|
707
|
+
const wxrp = net.swapWxrp || net.wxrp
|
|
708
|
+
|
|
709
|
+
// Get pair address
|
|
710
|
+
const pairAddress = await readContract({
|
|
711
|
+
address: net.swapFactory, abi: SWAP_FACTORY_ABI, functionName: 'getPair', args: [tokenAddress, wxrp],
|
|
712
|
+
})
|
|
713
|
+
const zeroPair = '0x0000000000000000000000000000000000000000'
|
|
714
|
+
if (!pairAddress || pairAddress === zeroPair) {
|
|
715
|
+
throw new Error(`No pool exists for this token/XRP pair.`)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Get LP balance, reserves, pair info
|
|
719
|
+
const [lpBalance, reserves, token0Addr, lpTotalSupply, tokenDecimals, tokenSymbol] = await Promise.all([
|
|
720
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'balanceOf', args: [account.address] }),
|
|
721
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'getReserves', args: [] }),
|
|
722
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'token0', args: [] }),
|
|
723
|
+
readContract({ address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'totalSupply', args: [] }),
|
|
724
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'decimals', args: [] }),
|
|
725
|
+
readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'symbol', args: [] }),
|
|
726
|
+
])
|
|
727
|
+
|
|
728
|
+
let lpAmount
|
|
729
|
+
if (lpAmountStr.toLowerCase() === 'all') {
|
|
730
|
+
lpAmount = lpBalance
|
|
731
|
+
if (lpAmount === 0n) { console.log(`\n No LP tokens for ${tokenSymbol}/XRP pool.`); return }
|
|
732
|
+
} else {
|
|
733
|
+
lpAmount = viem.parseEther(lpAmountStr)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (lpAmount > lpBalance) {
|
|
737
|
+
console.error(`Error: Insufficient LP balance. You have ${parseFloat(viem.formatEther(lpBalance)).toFixed(6)} LP tokens`)
|
|
738
|
+
process.exit(1)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Estimate what you'll receive
|
|
742
|
+
const isToken0 = token0Addr.toLowerCase() === tokenAddress.toLowerCase()
|
|
743
|
+
const tokenReserve = isToken0 ? reserves[0] : reserves[1]
|
|
744
|
+
const xrpReserve = isToken0 ? reserves[1] : reserves[0]
|
|
745
|
+
|
|
746
|
+
if (lpTotalSupply === 0n) throw new Error('Pool total supply is zero')
|
|
747
|
+
|
|
748
|
+
const expectedToken = lpAmount * tokenReserve / lpTotalSupply
|
|
749
|
+
const expectedXrp = lpAmount * xrpReserve / lpTotalSupply
|
|
750
|
+
|
|
751
|
+
const tokenFormatted = parseFloat(viem.formatUnits(expectedToken, tokenDecimals)).toLocaleString()
|
|
752
|
+
const xrpFormatted = parseFloat(viem.formatEther(expectedXrp)).toFixed(4)
|
|
753
|
+
const lpFormatted = parseFloat(viem.formatEther(lpAmount)).toFixed(6)
|
|
754
|
+
|
|
755
|
+
// Slippage minimums
|
|
756
|
+
const amountTokenMin = expectedToken * (10000n - slippageBps) / 10000n
|
|
757
|
+
const amountETHMin = expectedXrp * (10000n - slippageBps) / 10000n
|
|
758
|
+
|
|
759
|
+
console.log(`\n Removing liquidity on ${chain}`)
|
|
760
|
+
console.log(` ─────────────────────────────────────`)
|
|
761
|
+
console.log(` Pool: ${tokenSymbol} / XRP`)
|
|
762
|
+
console.log(` LP tokens: ${lpFormatted}`)
|
|
763
|
+
console.log(` Receive: ~${tokenFormatted} ${tokenSymbol} + ~${xrpFormatted} XRP`)
|
|
764
|
+
console.log(` Slippage: ${slippagePct}%`)
|
|
765
|
+
|
|
766
|
+
// Approve LP token for router
|
|
767
|
+
const lpAllowance = await readContract({
|
|
768
|
+
address: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'allowance', args: [account.address, net.swapRouter],
|
|
769
|
+
})
|
|
770
|
+
if (lpAllowance < lpAmount) {
|
|
771
|
+
console.log(' Approving LP tokens...')
|
|
772
|
+
const maxUint = 2n ** 256n - 1n
|
|
773
|
+
const { hash: approveHash } = await sendContractTx({
|
|
774
|
+
to: pairAddress, abi: SWAP_PAIR_ABI, functionName: 'approve', args: [net.swapRouter, maxUint],
|
|
775
|
+
})
|
|
776
|
+
const pub = await getPublicClient()
|
|
777
|
+
await pub.waitForTransactionReceipt({ hash: approveHash, timeout: 30000 })
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200) // 20 min
|
|
781
|
+
|
|
782
|
+
console.log(' Sending transaction...')
|
|
783
|
+
const { hash, explorer } = await sendContractTx({
|
|
784
|
+
to: net.swapRouter,
|
|
785
|
+
abi: SWAP_ROUTER_ABI,
|
|
786
|
+
functionName: 'removeLiquidityETH',
|
|
787
|
+
args: [tokenAddress, lpAmount, amountTokenMin, amountETHMin, account.address, deadline],
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
console.log(`\n Removed liquidity: ~${tokenFormatted} ${tokenSymbol} + ~${xrpFormatted} XRP`)
|
|
791
|
+
console.log(` TX: ${explorer}`)
|
|
792
|
+
}
|
|
793
|
+
|
|
588
794
|
// ─── Trade commands (perps) ──────────────────────────────────────────
|
|
589
795
|
|
|
590
796
|
function requirePerps() {
|
|
@@ -1489,6 +1695,8 @@ Swap (DEX):
|
|
|
1489
1695
|
swap <amount> <from> <to> Swap tokens (e.g. swap 1 XRP RLUSD)
|
|
1490
1696
|
swap quote <amount> <from> <to> Get a quote without executing
|
|
1491
1697
|
swap pools List available pools
|
|
1698
|
+
swap add-liquidity <token> <xrp> Add liquidity to a token/XRP pool
|
|
1699
|
+
swap remove-liquidity <token> <lp|all> Remove liquidity from a pool
|
|
1492
1700
|
|
|
1493
1701
|
Trade (Perps — testnet):
|
|
1494
1702
|
trade long <usd> [leverage] Open long (e.g. trade long 100 5x)
|
|
@@ -1547,6 +1755,8 @@ async function main() {
|
|
|
1547
1755
|
} else if (cmd === 'swap') {
|
|
1548
1756
|
if (sub === 'quote' || sub === 'q') await cmdSwapQuote(args[2], args[3], args[4])
|
|
1549
1757
|
else if (sub === 'pools' || sub === 'p') await cmdSwapPools()
|
|
1758
|
+
else if (sub === 'add-liquidity' || sub === 'al') await cmdSwapAddLiquidity(args[2], args[3])
|
|
1759
|
+
else if (sub === 'remove-liquidity' || sub === 'rl') await cmdSwapRemoveLiquidity(args[2], args[3])
|
|
1550
1760
|
else await cmdSwap(sub, args[2], args[3]) // swap <amount> <from> <to>
|
|
1551
1761
|
} else if (cmd === 'trade' || cmd === 't') {
|
|
1552
1762
|
if (sub === 'long' || sub === 'l') await cmdTradeOpen(true, args[2], args[3])
|
package/package.json
CHANGED