package pump_parser import ( "bytes" "fmt" agbinary "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) type meteoraDlmmSwapArgs struct { AmountIn uint64 MinAmountOut uint64 } type meteoraDlmmSwapExactOutArgs struct { MaxInAmount uint64 OutAmount uint64 } type meteoraDlmmSwapWithPriceImpactArgs struct { AmountIn uint64 ActiveID *int32 `bin:"optional"` MaxPriceImpactBps uint16 } type dlmmSwapEvent struct { LbPair solana.PublicKey From solana.PublicKey StartBinId int32 EndBinId int32 AmountIn uint64 AmountOut uint64 SwapForY bool Fee uint64 ProtocolFee uint64 FeeBps agbinary.Uint128 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 reserveYIdx int userTokenInIdx int userTokenOutIdx int tokenXMintIdx int tokenYMintIdx int oracleIdx int userIdx int tokenXProgramIdx int 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 { return solana.PublicKey{} } return key }() func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(meteoraDlmmProgram) { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm program instruction not found, offset, %d, %d", offset[0], offset[1]) } decode := instruction.Data if len(decode) < 8 { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm program instruction data too short, offset, %d, %d", offset[0], offset[1]) } discriminator := *(*[8]byte)(decode[:8]) switch discriminator { case meteoraInitializeLbPairDiscriminator: return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset) case meteoraDlmmSwapDiscriminator, meteoraDlmmSwapExactOutDiscriminator, meteoraDlmmSwapWithPriceImpactDiscriminator: 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 } } func metaoradlmmInitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { market := tx.rawTx.accountList[instruction.Accounts[0]] token0 := tx.rawTx.accountList[instruction.Accounts[2]] token1 := tx.rawTx.accountList[instruction.Accounts[3]] entryContract := tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] var baseDecimals uint8 var quoteDecimals uint8 for _, acc := range tx.rawTx.Meta.PostTokenBalances { if acc.MintAccount.Equals(token0) { baseDecimals = uint8(acc.UITokenAmount.Decimals) } if acc.MintAccount.Equals(token1) { quoteDecimals = uint8(acc.UITokenAmount.Decimals) } } swap := Swap{ Program: SolProgramMeteoraDLMM, Event: "create", Pool: market, BaseMint: token0, QuoteMint: token1, BaseTokenProgram: tx.rawTx.accountList[instruction.Accounts[11]], QuoteTokenProgram: tx.rawTx.accountList[instruction.Accounts[12]], Creator: tx.rawTx.accountList[0], BaseMintDecimals: baseDecimals, QuoteMintDecimals: quoteDecimals, User: tx.rawTx.accountList[instruction.Accounts[8]], EntryContract: entryContract, } var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } var programIndex = instruction.ProgramIDIndex for innerIndex, innerInstr := range inners { if innerInstr.ProgramIDIndex == programIndex && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraInitializeLbPairEventDiscriminator[:]) { if offset[1] == 0 { offset[0] += 1 } else { offset[1] = uint(innerIndex) + 1 + prefixLen } break } } return []Swap{swap}, offset, nil } func metaoradlmmSwapParser(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 { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap instruction data too short, offset, %d, %d", offset[0], offset[1]) } discriminator := *(*[8]byte)(decode[:8]) switch discriminator { case meteoraDlmmSwapDiscriminator, meteoraDlmmSwap2Discriminator: var args meteoraDlmmSwapArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } case meteoraDlmmSwapExactOutDiscriminator, meteoraDlmmSwapExactOut2Discriminator: var args meteoraDlmmSwapExactOutArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap_exact_out decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } case meteoraDlmmSwapWithPriceImpactDiscriminator, meteoraDlmmSwapWithPriceImpact2Discriminator: var args meteoraDlmmSwapWithPriceImpactArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap_with_price_impact decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } default: return nil, increaseOffset(offset), InstructionIgnoredError } accounts, err := resolveDlmmSwapAccounts(result, instruction.Accounts) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) } pool := result.accountList[accounts.poolIdx] reserveXIdx := accounts.reserveXIdx reserveYIdx := accounts.reserveYIdx userTokenInIdx := accounts.userTokenInIdx userTokenOutIdx := accounts.userTokenOutIdx tokenXMint := result.accountList[accounts.tokenXMintIdx] tokenYMint := result.accountList[accounts.tokenYMintIdx] userIdx := accounts.userIdx tokenXProgram := result.accountList[accounts.tokenXProgramIdx] tokenYProgram := result.accountList[accounts.tokenYProgramIdx] var prefixLen = offset[1] var swapEvent dlmmSwapEvent inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm swap get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen) } for innerIndex, innerInstr := range inners { if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex { continue } if len(innerInstr.Data) < 16 || !bytes.Equal(innerInstr.Data[:8], eventDiscriminator[:]) || !bytes.Equal(innerInstr.Data[8:16], meteoraDlmmSwapEventDiscriminator[:]) { continue } if offset[1] == 0 { offset[0] += 1 } else { offset[1] = uint(innerIndex) + 1 + prefixLen } if err := agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&swapEvent); err != nil { return nil, offset, fmt.Errorf("meteora dlmm swap event decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } break } baseMint, quoteMint, baseIsX := dlmmSelectBaseQuote(tokenXMint, tokenYMint) baseTokenProgram := tokenXProgram quoteTokenProgram := tokenYProgram baseReserveIdx := reserveXIdx quoteReserveIdx := reserveYIdx if !baseIsX { baseTokenProgram = tokenYProgram quoteTokenProgram = tokenXProgram baseReserveIdx = reserveYIdx quoteReserveIdx = reserveXIdx } swapForY := swapEvent.SwapForY inputIsX := swapForY amountIn := decimal.NewFromUint64(swapEvent.AmountIn) amountOut := decimal.NewFromUint64(swapEvent.AmountOut) event := "buy" if baseIsX == inputIsX { event = "sell" } userBaseIdx := userTokenOutIdx userQuoteIdx := userTokenInIdx if baseIsX == inputIsX { userBaseIdx = userTokenInIdx userQuoteIdx = userTokenOutIdx } baseAmount := amountOut quoteAmount := amountIn if baseIsX { if swapForY { baseAmount = amountIn quoteAmount = amountOut } } else { if !swapForY { baseAmount = amountIn quoteAmount = amountOut } } eventUser := result.accountList[userIdx] if !swapEvent.From.IsZero() { eventUser = swapEvent.From } if !eventUser.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) { userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint) if !userBaseAmount.IsZero() { eventUser = result.accountList[0] userIdx = 0 if ataIndex > 0 { userBaseIdx = ataIndex } } } 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 := GetTokenBalanceAfterTx(result, userIdx, quoteTokenProgram, quoteMint) if quoteMint.Equals(wSolMint) { if solAmount, err := GetSolAfterTx(result, userIdx); err == nil { userQuote = userQuote.Add(decimal.NewFromUint64(solAmount)) } } swap := Swap{ Program: SolProgramMeteoraDLMM, Event: event, Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseTokenProgram, QuoteTokenProgram: quoteTokenProgram, Creator: solana.PublicKey{}, BaseMintDecimals: baseDecimals, QuoteMintDecimals: quoteDecimals, User: eventUser, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, StartBinId: swapEvent.StartBinId, EndBinId: swapEvent.EndBinId, } return []Swap{swap}, offset, nil } func metaoradlmmSwap2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { 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 { if tokenX.Equals(mint) { return tokenY, tokenX, false } if tokenY.Equals(mint) { return tokenX, tokenY, true } } return tokenX, tokenY, true } 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 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") } accountList := result.accountList basePosCandidates := []int{1, 2} for _, basePos := range basePosCandidates { if basePos+6 >= len(accounts) { continue } oraclePos := basePos + 6 userPos := oraclePos + 1 hostFeePresent := true if userPos < len(accounts) && dlmmIsSigner(result, accounts[userPos]) { hostFeePresent = false } else { userPos = oraclePos + 2 } if userPos+2 >= len(accounts) { continue } tokenXProgramPos := userPos + 1 tokenYProgramPos := userPos + 2 eventAuthorityPos := tokenYProgramPos + 1 if eventAuthorityPos < len(accounts) && accountList[accounts[eventAuthorityPos]].Equals(solana.MemoProgramID) { eventAuthorityPos++ } programPos := eventAuthorityPos + 1 if programPos >= len(accounts) { continue } if !accountList[accounts[eventAuthorityPos]].Equals(meteoraDlmmEventAuthority) { continue } if !accountList[accounts[programPos]].Equals(meteoraDlmmProgram) { continue } if hostFeePresent && oraclePos+1 < len(accounts) && dlmmIsSigner(result, accounts[oraclePos+1]) { continue } return dlmmSwapAccounts{ poolIdx: accounts[0], reserveXIdx: accounts[oraclePos-6], reserveYIdx: accounts[oraclePos-5], userTokenInIdx: accounts[oraclePos-4], userTokenOutIdx: accounts[oraclePos-3], tokenXMintIdx: accounts[oraclePos-2], tokenYMintIdx: accounts[oraclePos-1], oracleIdx: accounts[oraclePos], userIdx: accounts[userPos], tokenXProgramIdx: accounts[tokenXProgramPos], tokenYProgramIdx: accounts[tokenYProgramPos], }, nil } 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 { return uint8(meta.UITokenAmount.Decimals), true } } for _, meta := range result.Meta.PreTokenBalances { if meta.AccountIndex == accountIndex { return uint8(meta.UITokenAmount.Decimals), true } } return 0, false } func dlmmTokenDelta(result *RawTx, accountIndex int) (decimal.Decimal, bool) { before, okBefore := dlmmTokenAmount(result, accountIndex, false) after, okAfter := dlmmTokenAmount(result, accountIndex, true) if !okBefore && !okAfter { return decimal.Zero, false } if !okBefore { before = decimal.Zero } if !okAfter { after = decimal.Zero } return after.Sub(before).Abs(), true } func dlmmTokenDeltaSigned(result *RawTx, accountIndex int) (decimal.Decimal, bool) { before, okBefore := dlmmTokenAmount(result, accountIndex, false) after, okAfter := dlmmTokenAmount(result, accountIndex, true) if !okBefore && !okAfter { return decimal.Zero, false } if !okBefore { before = decimal.Zero } if !okAfter { after = decimal.Zero } return after.Sub(before), true } func dlmmTokenAmount(result *RawTx, accountIndex int, post bool) (decimal.Decimal, bool) { var balances []TokenBalance if post { balances = result.Meta.PostTokenBalances } else { balances = result.Meta.PreTokenBalances } for _, meta := range balances { if meta.AccountIndex == accountIndex { amount, err := decimal.NewFromString(meta.UITokenAmount.Amount) if err != nil { return decimal.Zero, false } return amount, true } } return decimal.Zero, false } func dlmmIsSigner(result *RawTx, accountIndex int) bool { if accountIndex < 0 || accountIndex >= len(result.Transaction.Message.AccountKeys) { return false } return accountIndex < result.Transaction.Message.Header.NumRequiredSignatures } func dlmmTokenBalanceMeta(result *RawTx, accountIndex int) (TokenBalance, bool) { for _, meta := range result.Meta.PostTokenBalances { if meta.AccountIndex == accountIndex { return meta, true } } for _, meta := range result.Meta.PreTokenBalances { if meta.AccountIndex == accountIndex { return meta, true } } 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 }