|
@@ -203,7 +203,8 @@ export async function copyRebalanceLiquidity(
|
|
|
let minBinId = leaderOp.minBinId ?? 0
|
|
let minBinId = leaderOp.minBinId ?? 0
|
|
|
let maxBinId = leaderOp.maxBinId ?? 0
|
|
let maxBinId = leaderOp.maxBinId ?? 0
|
|
|
|
|
|
|
|
- // If we have delta-based adds, compute absolute bin IDs from current active bin
|
|
|
|
|
|
|
+ // If we have delta-based adds, compute absolute bin IDs from the current active bin
|
|
|
|
|
+ // (not the leader's active bin, since it may have moved)
|
|
|
if (leaderOp.rebalanceAdds && leaderOp.rebalanceAdds.length > 0) {
|
|
if (leaderOp.rebalanceAdds && leaderOp.rebalanceAdds.length > 0) {
|
|
|
const allMinBins = leaderOp.rebalanceAdds.map(
|
|
const allMinBins = leaderOp.rebalanceAdds.map(
|
|
|
(a) => activeId + a.minDeltaId
|
|
(a) => activeId + a.minDeltaId
|
|
@@ -215,22 +216,88 @@ export async function copyRebalanceLiquidity(
|
|
|
maxBinId = Math.max(...allMaxBins)
|
|
maxBinId = Math.max(...allMaxBins)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Validate bin range — if both are 0, the parameter extraction failed
|
|
|
|
|
+ if (minBinId === 0 && maxBinId === 0 && activeId !== 0) {
|
|
|
|
|
+ throw new Error(
|
|
|
|
|
+ `Rebalance failed: could not determine target bin range (activeBin=${activeId}). ` +
|
|
|
|
|
+ `Leader rebalanceAdds: ${JSON.stringify(leaderOp.rebalanceAdds)}`
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
console.log(
|
|
console.log(
|
|
|
`[DLMM] Rebalancing: new bins [${minBinId}, ${maxBinId}], activeBin ${activeId}`
|
|
`[DLMM] Rebalancing: new bins [${minBinId}, ${maxBinId}], activeBin ${activeId}`
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- // Scale amounts by copy ratio
|
|
|
|
|
- const followerAmountX = scaleAmount(
|
|
|
|
|
- leaderOp.totalXAmount ?? new BN(0),
|
|
|
|
|
- settings.copyRatio
|
|
|
|
|
- )
|
|
|
|
|
- const followerAmountY = scaleAmount(
|
|
|
|
|
- leaderOp.totalYAmount ?? new BN(0),
|
|
|
|
|
- settings.copyRatio
|
|
|
|
|
|
|
+ // For rebalance, use the follower's actual available token balances
|
|
|
|
|
+ // (not the leader's amounts — those are just safety caps from the IDL).
|
|
|
|
|
+ // After step 1 removed all liquidity, the tokens are in the follower's accounts.
|
|
|
|
|
+ const WSOL_MINT = new PublicKey(
|
|
|
|
|
+ 'So11111111111111111111111111111111111111112'
|
|
|
)
|
|
)
|
|
|
|
|
+ const tokenXMint = dlmmPool.tokenX.publicKey
|
|
|
|
|
+ const tokenYMint = dlmmPool.tokenY.publicKey
|
|
|
|
|
+
|
|
|
|
|
+ const SOL_RESERVE = 10_000_000 // Keep 0.01 SOL for tx fees
|
|
|
|
|
+
|
|
|
|
|
+ let followerAmountX = new BN(0)
|
|
|
|
|
+ let followerAmountY = new BN(0)
|
|
|
|
|
+
|
|
|
|
|
+ // Token X balance
|
|
|
|
|
+ if (tokenXMint.equals(WSOL_MINT)) {
|
|
|
|
|
+ const balance = await connection.getBalance(follower.publicKey)
|
|
|
|
|
+ followerAmountX = new BN(Math.max(0, balance - SOL_RESERVE))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const xAccounts = await connection.getParsedTokenAccountsByOwner(
|
|
|
|
|
+ follower.publicKey,
|
|
|
|
|
+ { mint: tokenXMint }
|
|
|
|
|
+ )
|
|
|
|
|
+ for (const { account } of xAccounts.value) {
|
|
|
|
|
+ followerAmountX = followerAmountX.add(
|
|
|
|
|
+ new BN(
|
|
|
|
|
+ (
|
|
|
|
|
+ account.data.parsed as {
|
|
|
|
|
+ info: { tokenAmount: { amount: string } }
|
|
|
|
|
+ }
|
|
|
|
|
+ ).info.tokenAmount.amount
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Token Y balance
|
|
|
|
|
+ if (tokenYMint.equals(WSOL_MINT)) {
|
|
|
|
|
+ const balance = await connection.getBalance(follower.publicKey)
|
|
|
|
|
+ followerAmountY = new BN(Math.max(0, balance - SOL_RESERVE))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const yAccounts = await connection.getParsedTokenAccountsByOwner(
|
|
|
|
|
+ follower.publicKey,
|
|
|
|
|
+ { mint: tokenYMint }
|
|
|
|
|
+ )
|
|
|
|
|
+ for (const { account } of yAccounts.value) {
|
|
|
|
|
+ followerAmountY = followerAmountY.add(
|
|
|
|
|
+ new BN(
|
|
|
|
|
+ (
|
|
|
|
|
+ account.data.parsed as {
|
|
|
|
|
+ info: { tokenAmount: { amount: string } }
|
|
|
|
|
+ }
|
|
|
|
|
+ ).info.tokenAmount.amount
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (followerAmountX.isZero() && followerAmountY.isZero()) {
|
|
|
|
|
+ throw new Error(
|
|
|
|
|
+ 'Rebalance failed: follower has no token balance to re-deposit after removing liquidity.'
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
const sdkStrategyType = toSdkStrategyType(leaderOp.strategyType)
|
|
const sdkStrategyType = toSdkStrategyType(leaderOp.strategyType)
|
|
|
|
|
|
|
|
|
|
+ console.log(
|
|
|
|
|
+ `[DLMM] Rebalance addLiquidity: strategy ${sdkStrategyType}, amountX ${followerAmountX.toString()}, amountY ${followerAmountY.toString()}`
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
const addLiqTx = await dlmmPool.addLiquidityByStrategy({
|
|
const addLiqTx = await dlmmPool.addLiquidityByStrategy({
|
|
|
positionPubKey: followerPositionAddress,
|
|
positionPubKey: followerPositionAddress,
|
|
|
user: follower.publicKey,
|
|
user: follower.publicKey,
|