From 6b4cadb1182ef7ff5d42c063b4b90890325f0783 Mon Sep 17 00:00:00 2001 From: bijianing97 <826015751@qq.com> Date: Thu, 15 Jan 2026 17:44:39 +0800 Subject: [PATCH] Update --- meta.go | 10 + metaoradlmm.go | 718 +++++++++++++++++++++++++++++++++++++++++++++++++ tx.go | 8 + 3 files changed, 736 insertions(+) diff --git a/meta.go b/meta.go index f0aee3b..932a866 100644 --- a/meta.go +++ b/meta.go @@ -74,6 +74,16 @@ var ( meteoraDlmmSwapWithPriceImpactDiscriminator = calculateDiscriminator("global:swap_with_price_impact") meteoraDlmmSwapWithPriceImpact2Discriminator = calculateDiscriminator("global:swap_with_price_impact2") meteoraDlmmSwapEventDiscriminator = calculateDiscriminator("event:Swap") + meteoraDlmmAddLiquidityDiscriminator = calculateDiscriminator("global:add_liquidity") + meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2") + meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy") + meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2") + meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity") + meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2") + meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range") + meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2") + meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity") + meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity") ) // Program PumpAmm program ID diff --git a/metaoradlmm.go b/metaoradlmm.go index 3ac917a..b55bb92 100644 --- a/metaoradlmm.go +++ b/metaoradlmm.go @@ -39,6 +39,118 @@ type dlmmSwapEvent struct { HostFee uint64 } +type dlmmAddLiquidityEvent struct { + LbPair solana.PublicKey + From solana.PublicKey + Position solana.PublicKey + Amounts [2]uint64 + ActiveBinId int32 +} + +type dlmmRemoveLiquidityEvent struct { + LbPair solana.PublicKey + From solana.PublicKey + Position solana.PublicKey + Amounts [2]uint64 + ActiveBinId int32 +} + +type dlmmBinLiquidityDistribution struct { + BinId int32 + DistributionX uint16 + DistributionY uint16 +} + +type dlmmBinLiquidityReduction struct { + BinId int32 + BpsToRemove uint16 +} + +type dlmmLiquidityParameter struct { + AmountX uint64 + AmountY uint64 + BinLiquidityDist []dlmmBinLiquidityDistribution +} + +type dlmmStrategyParameters struct { + MinBinId int32 + MaxBinId int32 + StrategyType uint8 + Parameters [64]byte +} + +type dlmmLiquidityParameterByStrategy struct { + AmountX uint64 + AmountY uint64 + ActiveID int32 + MaxActiveBinSlippage int32 + StrategyParameters dlmmStrategyParameters +} + +type dlmmAddLiquidityArgs struct { + LiquidityParameter dlmmLiquidityParameter +} + +type dlmmAddLiquidity2Args struct { + LiquidityParameter dlmmLiquidityParameter + RemainingAccountsInfo dlmmRemainingAccountsInfo +} + +type dlmmAddLiquidityByStrategyArgs struct { + LiquidityParameter dlmmLiquidityParameterByStrategy +} + +type dlmmAddLiquidityByStrategy2Args struct { + LiquidityParameter dlmmLiquidityParameterByStrategy + RemainingAccountsInfo dlmmRemainingAccountsInfo +} + +type dlmmRemoveLiquidityArgs struct { + BinLiquidityRemoval []dlmmBinLiquidityReduction +} + +type dlmmRemoveLiquidity2Args struct { + BinLiquidityRemoval []dlmmBinLiquidityReduction + RemainingAccountsInfo dlmmRemainingAccountsInfo +} + +type dlmmRemoveLiquidityByRangeArgs struct { + FromBinId int32 + ToBinId int32 + BpsToRemove uint16 +} + +type dlmmRemoveLiquidityByRange2Args struct { + FromBinId int32 + ToBinId int32 + BpsToRemove uint16 + RemainingAccountsInfo dlmmRemainingAccountsInfo +} + +type dlmmRemainingAccountsInfo struct{} + +func (dlmmRemainingAccountsInfo) UnmarshalWithDecoder(decoder *agbinary.Decoder) error { + count, err := decoder.ReadUint32(agbinary.LE) + if err != nil { + return err + } + for i := uint32(0); i < count; i++ { + variant, err := decoder.ReadUint8() + if err != nil { + return err + } + if variant == 3 { + if _, err := decoder.ReadUint8(); err != nil { + return err + } + } + if _, err := decoder.ReadUint8(); err != nil { + return err + } + } + return nil +} + type dlmmSwapAccounts struct { poolIdx int reserveXIdx int @@ -53,6 +165,20 @@ type dlmmSwapAccounts struct { tokenYProgramIdx int } +type dlmmLiquidityAccounts struct { + positionIdx int + poolIdx int + userTokenXIdx int + userTokenYIdx int + reserveXIdx int + reserveYIdx int + tokenXMintIdx int + tokenYMintIdx int + userIdx int + tokenXProgramIdx int + tokenYProgramIdx int +} + var meteoraDlmmEventAuthority = func() solana.PublicKey { key, _, err := solana.FindProgramAddress([][]byte{[]byte("__event_authority")}, meteoraDlmmProgram) if err != nil { @@ -77,6 +203,12 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset) case meteoraDlmmSwap2Discriminator, meteoraDlmmSwapExactOut2Discriminator, meteoraDlmmSwapWithPriceImpact2Discriminator: return metaoradlmmSwap2Parser(tx, instruction, innerInstructions, offset) + case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator, + meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator: + return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset) + case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator, + meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator: + return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset) default: return nil, increaseOffset(offset), InstructionIgnoredError } @@ -264,6 +396,362 @@ func metaoradlmmSwap2Parser(tx *Tx, instruction Instruction, innerInstructions I return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset) } +func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { + result := tx.rawTx + + entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] + if instruction.StackHeight != nil && *instruction.StackHeight > 2 { + for _, innerInstr := range innerInstructions.Instructions { + if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 { + entryContract = result.accountList[innerInstr.ProgramIDIndex] + } + } + } + + decode := instruction.Data + if len(decode) < 8 { + offset[1] += 1 + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity instruction data too short, offset, %d, %d", offset[0], offset[1]) + } + + discriminator := *(*[8]byte)(decode[:8]) + var ( + amountX uint64 + amountY uint64 + binDist []dlmmBinLiquidityDistribution + startBinId int32 + endBinId int32 + hasRange bool + ) + + switch discriminator { + case meteoraDlmmAddLiquidityDiscriminator: + var args dlmmAddLiquidityArgs + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + amountX = args.LiquidityParameter.AmountX + amountY = args.LiquidityParameter.AmountY + binDist = args.LiquidityParameter.BinLiquidityDist + startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist) + hasRange = len(binDist) > 0 + case meteoraDlmmAddLiquidity2Discriminator: + var args dlmmAddLiquidity2Args + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity2 decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + amountX = args.LiquidityParameter.AmountX + amountY = args.LiquidityParameter.AmountY + binDist = args.LiquidityParameter.BinLiquidityDist + startBinId, endBinId = dlmmMinMaxBinIdFromDistribution(binDist) + hasRange = len(binDist) > 0 + case meteoraDlmmAddLiquidityByStrategyDiscriminator: + var args dlmmAddLiquidityByStrategyArgs + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by strategy decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + amountX = args.LiquidityParameter.AmountX + amountY = args.LiquidityParameter.AmountY + startBinId = args.LiquidityParameter.StrategyParameters.MinBinId + endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId + hasRange = true + case meteoraDlmmAddLiquidityByStrategy2Discriminator: + var args dlmmAddLiquidityByStrategy2Args + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by strategy2 decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + amountX = args.LiquidityParameter.AmountX + amountY = args.LiquidityParameter.AmountY + startBinId = args.LiquidityParameter.StrategyParameters.MinBinId + endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId + hasRange = true + default: + return nil, increaseOffset(offset), InstructionIgnoredError + } + + accounts, err := resolveDlmmLiquidityAccounts(result, instruction.Accounts) + if err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + + addEvent, nextOffset, err := dlmmAddLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset) + if err != nil { + return nil, nextOffset, err + } + offset = nextOffset + amountX = addEvent.Amounts[0] + amountY = addEvent.Amounts[1] + + binChanges := []DlmmBinLiquidityChange(nil) + if len(binDist) > 0 { + binChanges = dlmmBinChangesFromDistribution(amountX, amountY, binDist) + } else if hasRange { + binChanges = dlmmBinChangesFromRange(startBinId, endBinId, 0) + } + + pool := result.accountList[accounts.poolIdx] + tokenXMint := result.accountList[accounts.tokenXMintIdx] + tokenYMint := result.accountList[accounts.tokenYMintIdx] + tokenXProgram := result.accountList[accounts.tokenXProgramIdx] + tokenYProgram := result.accountList[accounts.tokenYProgramIdx] + + baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint) + baseTokenProgram := tokenXProgram + quoteTokenProgram := tokenYProgram + baseReserveIdx := accounts.reserveXIdx + quoteReserveIdx := accounts.reserveYIdx + userBaseIdx := accounts.userTokenXIdx + userQuoteIdx := accounts.userTokenYIdx + if !baseIsX { + baseTokenProgram = tokenYProgram + quoteTokenProgram = tokenXProgram + baseReserveIdx = accounts.reserveYIdx + quoteReserveIdx = accounts.reserveXIdx + userBaseIdx = accounts.userTokenYIdx + userQuoteIdx = accounts.userTokenXIdx + } + + amountXDec := decimal.NewFromUint64(amountX) + amountYDec := decimal.NewFromUint64(amountY) + baseAmount := amountXDec + quoteAmount := amountYDec + if !baseIsX { + baseAmount = amountYDec + quoteAmount = amountXDec + } + + eventUser := result.accountList[accounts.userIdx] + if !addEvent.From.IsZero() { + eventUser = addEvent.From + } + + baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx) + if !ok { + baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx) + } + quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx) + if !ok { + quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx) + } + + if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { + tx.Token[baseMint] = TokenMeta{ + Mint: baseMint, + Decimals: baseDecimals, + TokenProgram: baseTokenProgram, + } + } + if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { + tx.Token[quoteMint] = TokenMeta{ + Mint: quoteMint, + Decimals: quoteDecimals, + TokenProgram: quoteTokenProgram, + } + } + + baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx) + quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx) + userBase := getAccountBalanceAfterTx(result, userBaseIdx) + userQuote := getAccountBalanceAfterTx(result, userQuoteIdx) + if quoteMint.Equals(wSolMint) { + if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil { + userQuote = userQuote.Add(decimal.NewFromUint64(solAmount)) + } + } + + swap := Swap{ + Program: SolProgramMeteoraDLMM, + Event: "add_liquidity", + Pool: pool, + BaseMint: baseMint, + QuoteMint: quoteMint, + BaseTokenProgram: baseTokenProgram, + QuoteTokenProgram: quoteTokenProgram, + BaseMintDecimals: baseDecimals, + QuoteMintDecimals: quoteDecimals, + User: eventUser, + BaseAmount: baseAmount, + QuoteAmount: quoteAmount, + BaseReserve: baseReserve, + QuoteReserve: quoteReserve, + UserBaseBalance: userBase, + UserQuoteBalance: userQuote, + EntryContract: entryContract, + StartBinId: startBinId, + EndBinId: endBinId, + BinChanges: binChanges, + } + + return []Swap{swap}, offset, nil +} + +func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { + result := tx.rawTx + + entryContract := result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] + if instruction.StackHeight != nil && *instruction.StackHeight > 2 { + for _, innerInstr := range innerInstructions.Instructions { + if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 { + entryContract = result.accountList[innerInstr.ProgramIDIndex] + } + } + } + + decode := instruction.Data + if len(decode) < 8 { + offset[1] += 1 + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity instruction data too short, offset, %d, %d", offset[0], offset[1]) + } + + discriminator := *(*[8]byte)(decode[:8]) + var ( + binChanges []DlmmBinLiquidityChange + startBinId int32 + endBinId int32 + ) + + switch discriminator { + case meteoraDlmmRemoveLiquidityDiscriminator: + var args dlmmRemoveLiquidityArgs + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval) + startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) + case meteoraDlmmRemoveLiquidity2Discriminator: + var args dlmmRemoveLiquidity2Args + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity2 decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval) + startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) + case meteoraDlmmRemoveLiquidityByRangeDiscriminator: + var args dlmmRemoveLiquidityByRangeArgs + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity by range decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + startBinId = args.FromBinId + endBinId = args.ToBinId + binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove) + case meteoraDlmmRemoveLiquidityByRange2Discriminator: + var args dlmmRemoveLiquidityByRange2Args + if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity by range2 decode error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + startBinId = args.FromBinId + endBinId = args.ToBinId + binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove) + default: + return nil, increaseOffset(offset), InstructionIgnoredError + } + + accounts, err := resolveDlmmLiquidityAccounts(result, instruction.Accounts) + if err != nil { + return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) + } + + removeEvent, nextOffset, err := dlmmRemoveLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset) + if err != nil { + return nil, nextOffset, err + } + offset = nextOffset + + pool := result.accountList[accounts.poolIdx] + tokenXMint := result.accountList[accounts.tokenXMintIdx] + tokenYMint := result.accountList[accounts.tokenYMintIdx] + tokenXProgram := result.accountList[accounts.tokenXProgramIdx] + tokenYProgram := result.accountList[accounts.tokenYProgramIdx] + + baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint) + baseTokenProgram := tokenXProgram + quoteTokenProgram := tokenYProgram + baseReserveIdx := accounts.reserveXIdx + quoteReserveIdx := accounts.reserveYIdx + userBaseIdx := accounts.userTokenXIdx + userQuoteIdx := accounts.userTokenYIdx + if !baseIsX { + baseTokenProgram = tokenYProgram + quoteTokenProgram = tokenXProgram + baseReserveIdx = accounts.reserveYIdx + quoteReserveIdx = accounts.reserveXIdx + userBaseIdx = accounts.userTokenYIdx + userQuoteIdx = accounts.userTokenXIdx + } + + amountXDec := decimal.NewFromUint64(removeEvent.Amounts[0]) + amountYDec := decimal.NewFromUint64(removeEvent.Amounts[1]) + baseAmount := amountXDec + quoteAmount := amountYDec + if !baseIsX { + baseAmount = amountYDec + quoteAmount = amountXDec + } + + eventUser := result.accountList[accounts.userIdx] + if !removeEvent.From.IsZero() { + eventUser = removeEvent.From + } + + baseDecimals, ok := dlmmTokenDecimals(result, baseReserveIdx) + if !ok { + baseDecimals, _ = dlmmTokenDecimals(result, userBaseIdx) + } + quoteDecimals, ok := dlmmTokenDecimals(result, quoteReserveIdx) + if !ok { + quoteDecimals, _ = dlmmTokenDecimals(result, userQuoteIdx) + } + + if _, exists := tx.Token[baseMint]; !exists && !baseMint.Equals(wSolMint) { + tx.Token[baseMint] = TokenMeta{ + Mint: baseMint, + Decimals: baseDecimals, + TokenProgram: baseTokenProgram, + } + } + if _, exists := tx.Token[quoteMint]; !exists && !quoteMint.Equals(wSolMint) { + tx.Token[quoteMint] = TokenMeta{ + Mint: quoteMint, + Decimals: quoteDecimals, + TokenProgram: quoteTokenProgram, + } + } + + baseReserve := getAccountBalanceAfterTx(result, baseReserveIdx) + quoteReserve := getAccountBalanceAfterTx(result, quoteReserveIdx) + userBase := getAccountBalanceAfterTx(result, userBaseIdx) + userQuote := getAccountBalanceAfterTx(result, userQuoteIdx) + if quoteMint.Equals(wSolMint) { + if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil { + userQuote = userQuote.Add(decimal.NewFromUint64(solAmount)) + } + } + + swap := Swap{ + Program: SolProgramMeteoraDLMM, + Event: "remove_liquidity", + Pool: pool, + BaseMint: baseMint, + QuoteMint: quoteMint, + BaseTokenProgram: baseTokenProgram, + QuoteTokenProgram: quoteTokenProgram, + BaseMintDecimals: baseDecimals, + QuoteMintDecimals: quoteDecimals, + User: eventUser, + BaseAmount: baseAmount, + QuoteAmount: quoteAmount, + BaseReserve: baseReserve, + QuoteReserve: quoteReserve, + UserBaseBalance: userBase, + UserQuoteBalance: userQuote, + EntryContract: entryContract, + StartBinId: startBinId, + EndBinId: endBinId, + BinChanges: binChanges, + } + + return []Swap{swap}, offset, nil +} + func dlmmSelectBaseQuote(tokenX, tokenY solana.PublicKey) (baseMint solana.PublicKey, quoteMint solana.PublicKey, baseIsX bool) { priority := []solana.PublicKey{wSolMint, usdcMint, usd1Mint} for _, mint := range priority { @@ -301,6 +789,54 @@ func dlmmSwapEventFromInnerInstructions(innerInstructions InnerInstructions, ins return dlmmSwapEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm swap event not found, offset, %d, %d", offset[0], prefixLen) } +func dlmmAddLiquidityEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmAddLiquidityEvent, [2]uint, error) { + var prefixLen = offset[1] + inners, err := getInnerInstructions(innerInstructions, prefixLen) + if err != nil { + return dlmmAddLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen) + } + for innerIndex, innerInstr := range inners { + if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex { + continue + } + event, ok := dlmmDecodeAddLiquidityEvent(innerInstr.Data) + if !ok { + continue + } + if offset[1] == 0 { + offset[0] += 1 + } else { + offset[1] += uint(innerIndex) + 1 + prefixLen + } + return event, offset, nil + } + return dlmmAddLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity event not found, offset, %d, %d", offset[0], prefixLen) +} + +func dlmmRemoveLiquidityEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmRemoveLiquidityEvent, [2]uint, error) { + var prefixLen = offset[1] + inners, err := getInnerInstructions(innerInstructions, prefixLen) + if err != nil { + return dlmmRemoveLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen) + } + for innerIndex, innerInstr := range inners { + if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex { + continue + } + event, ok := dlmmDecodeRemoveLiquidityEvent(innerInstr.Data) + if !ok { + continue + } + if offset[1] == 0 { + offset[0] += 1 + } else { + offset[1] += uint(innerIndex) + 1 + prefixLen + } + return event, offset, nil + } + return dlmmRemoveLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity event not found, offset, %d, %d", offset[0], prefixLen) +} + func dlmmDecodeSwapEvent(data []byte) (dlmmSwapEvent, bool) { switch { case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmSwapEventDiscriminator[:]): @@ -322,6 +858,48 @@ func dlmmDecodeSwapEvent(data []byte) (dlmmSwapEvent, bool) { } } +func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) { + switch { + case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]): + var event dlmmAddLiquidityEvent + if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil { + return dlmmAddLiquidityEvent{}, false + } + return event, true + case len(data) >= 16 && + bytes.Equal(data[:8], eventDiscriminator[:]) && + bytes.Equal(data[8:16], meteoraDlmmAddLiquidityEventDiscriminator[:]): + var event dlmmAddLiquidityEvent + if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { + return dlmmAddLiquidityEvent{}, false + } + return event, true + default: + return dlmmAddLiquidityEvent{}, false + } +} + +func dlmmDecodeRemoveLiquidityEvent(data []byte) (dlmmRemoveLiquidityEvent, bool) { + switch { + case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmRemoveLiquidityEventDiscriminator[:]): + var event dlmmRemoveLiquidityEvent + if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil { + return dlmmRemoveLiquidityEvent{}, false + } + return event, true + case len(data) >= 16 && + bytes.Equal(data[:8], eventDiscriminator[:]) && + bytes.Equal(data[8:16], meteoraDlmmRemoveLiquidityEventDiscriminator[:]): + var event dlmmRemoveLiquidityEvent + if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { + return dlmmRemoveLiquidityEvent{}, false + } + return event, true + default: + return dlmmRemoveLiquidityEvent{}, false + } +} + func resolveDlmmSwapAccounts(result *RawTx, accounts []int) (dlmmSwapAccounts, error) { if len(accounts) < 13 { return dlmmSwapAccounts{}, fmt.Errorf("accounts too short, expected at least 13") @@ -385,6 +963,60 @@ func resolveDlmmSwapAccounts(result *RawTx, accounts []int) (dlmmSwapAccounts, e return dlmmSwapAccounts{}, fmt.Errorf("accounts layout invalid") } +func resolveDlmmLiquidityAccounts(result *RawTx, accounts []int) (dlmmLiquidityAccounts, error) { + if len(accounts) < 9 { + return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 9") + } + accountList := result.accountList + + eventAuthorityPos := -1 + for i, idx := range accounts { + if idx < 0 || idx >= len(accountList) { + continue + } + if accountList[idx].Equals(meteoraDlmmEventAuthority) { + eventAuthorityPos = i + break + } + } + if eventAuthorityPos == -1 { + return dlmmLiquidityAccounts{}, fmt.Errorf("event authority not found") + } + if eventAuthorityPos+1 >= len(accounts) || !accountList[accounts[eventAuthorityPos+1]].Equals(meteoraDlmmProgram) { + return dlmmLiquidityAccounts{}, fmt.Errorf("program id not found after event authority") + } + + tokenYProgramPos := eventAuthorityPos - 1 + if tokenYProgramPos < 0 { + return dlmmLiquidityAccounts{}, fmt.Errorf("token program position invalid") + } + if accountList[accounts[tokenYProgramPos]].Equals(solana.MemoProgramID) { + tokenYProgramPos-- + } + if tokenYProgramPos < 1 { + return dlmmLiquidityAccounts{}, fmt.Errorf("token program positions invalid") + } + tokenXProgramPos := tokenYProgramPos - 1 + userPos := tokenXProgramPos - 1 + if userPos < 0 { + return dlmmLiquidityAccounts{}, fmt.Errorf("user position invalid") + } + + return dlmmLiquidityAccounts{ + positionIdx: accounts[0], + poolIdx: accounts[1], + userTokenXIdx: accounts[3], + userTokenYIdx: accounts[4], + reserveXIdx: accounts[5], + reserveYIdx: accounts[6], + tokenXMintIdx: accounts[7], + tokenYMintIdx: accounts[8], + userIdx: accounts[userPos], + tokenXProgramIdx: accounts[tokenXProgramPos], + tokenYProgramIdx: accounts[tokenYProgramPos], + }, nil +} + func dlmmTokenDecimals(result *RawTx, accountIndex int) (uint8, bool) { for _, meta := range result.Meta.PostTokenBalances { if meta.AccountIndex == accountIndex { @@ -468,3 +1100,89 @@ func dlmmTokenBalanceMeta(result *RawTx, accountIndex int) (TokenBalance, bool) } return TokenBalance{}, false } + +func dlmmBinChangesFromDistribution(amountX, amountY uint64, dist []dlmmBinLiquidityDistribution) []DlmmBinLiquidityChange { + if len(dist) == 0 { + return nil + } + totalX := decimal.NewFromUint64(amountX) + totalY := decimal.NewFromUint64(amountY) + denom := decimal.NewFromInt(10000) + changes := make([]DlmmBinLiquidityChange, 0, len(dist)) + for _, item := range dist { + x := totalX.Mul(decimal.NewFromInt(int64(item.DistributionX))).Div(denom).Truncate(0) + y := totalY.Mul(decimal.NewFromInt(int64(item.DistributionY))).Div(denom).Truncate(0) + changes = append(changes, DlmmBinLiquidityChange{ + BinId: item.BinId, + AmountX: x, + AmountY: y, + }) + } + return changes +} + +func dlmmBinChangesFromReduction(reduction []dlmmBinLiquidityReduction) []DlmmBinLiquidityChange { + if len(reduction) == 0 { + return nil + } + changes := make([]DlmmBinLiquidityChange, 0, len(reduction)) + for _, item := range reduction { + changes = append(changes, DlmmBinLiquidityChange{ + BinId: item.BinId, + BpsToRemove: item.BpsToRemove, + }) + } + return changes +} + +func dlmmBinChangesFromRange(startBinId, endBinId int32, bpsToRemove uint16) []DlmmBinLiquidityChange { + if startBinId > endBinId { + startBinId, endBinId = endBinId, startBinId + } + count := int(endBinId-startBinId) + 1 + if count <= 0 { + return nil + } + changes := make([]DlmmBinLiquidityChange, 0, count) + for binId := startBinId; binId <= endBinId; binId++ { + changes = append(changes, DlmmBinLiquidityChange{ + BinId: binId, + BpsToRemove: bpsToRemove, + }) + } + return changes +} + +func dlmmMinMaxBinIdFromDistribution(dist []dlmmBinLiquidityDistribution) (int32, int32) { + if len(dist) == 0 { + return 0, 0 + } + min := dist[0].BinId + max := dist[0].BinId + for _, item := range dist[1:] { + if item.BinId < min { + min = item.BinId + } + if item.BinId > max { + max = item.BinId + } + } + return min, max +} + +func dlmmMinMaxBinIdFromReduction(reduction []dlmmBinLiquidityReduction) (int32, int32) { + if len(reduction) == 0 { + return 0, 0 + } + min := reduction[0].BinId + max := reduction[0].BinId + for _, item := range reduction[1:] { + if item.BinId < min { + min = item.BinId + } + if item.BinId > max { + max = item.BinId + } + } + return min, max +} diff --git a/tx.go b/tx.go index 04bfe47..e180ae4 100644 --- a/tx.go +++ b/tx.go @@ -37,6 +37,14 @@ type Swap struct { //For meteora dlmm StartBinId int32 EndBinId int32 + BinChanges []DlmmBinLiquidityChange +} + +type DlmmBinLiquidityChange struct { + BinId int32 + AmountX decimal.Decimal + AmountY decimal.Decimal + BpsToRemove uint16 } type platformInfo struct {