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 dlmmPositionCreateEvent struct { LbPair solana.PublicKey Position solana.PublicKey Owner solana.PublicKey } type dlmmPositionCloseEvent struct { Position solana.PublicKey Owner solana.PublicKey } type dlmmLbPairCreateEvent struct { LbPair solana.PublicKey BinStep uint16 TokenX solana.PublicKey TokenY solana.PublicKey } type dlmmClaimFeeInnerEvent struct { LbPair solana.PublicKey Position solana.PublicKey Owner solana.PublicKey FeeX uint64 FeeY uint64 ActiveBinId int32 HasActiveBin bool } type dlmmClaimFeeEvent struct { LbPair solana.PublicKey Position solana.PublicKey Owner solana.PublicKey FeeX uint64 FeeY uint64 } type dlmmClaimFee2Event struct { LbPair solana.PublicKey Position solana.PublicKey Owner solana.PublicKey FeeX uint64 FeeY uint64 ActiveBinId int32 } type dlmmRebalancingEvent struct { LbPair solana.PublicKey Position solana.PublicKey Owner solana.PublicKey ActiveBinId int32 XWithdrawnAmount uint64 XAddedAmount uint64 YWithdrawnAmount uint64 YAddedAmount uint64 XFeeAmount uint64 YFeeAmount uint64 OldMinBinId int32 OldMaxBinId int32 NewMinBinId int32 NewMaxBinId int32 Rewards [2]uint64 } type dlmmBinLiquidityDistribution struct { BinId int32 DistributionX uint16 DistributionY uint16 } type dlmmBinLiquidityDistributionByWeight struct { BinId int32 Weight 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 dlmmLiquidityParameterByWeight struct { AmountX uint64 AmountY uint64 ActiveID int32 MaxActiveBinSlippage int32 BinLiquidityDist []dlmmBinLiquidityDistributionByWeight } 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 dlmmAddLiquidityByWeightArgs struct { LiquidityParameter dlmmLiquidityParameterByWeight } type dlmmLiquidityOneSideParameter struct { Amount uint64 ActiveID int32 MaxActiveBinSlippage int32 BinLiquidityDist []dlmmBinLiquidityDistributionByWeight } type dlmmLiquidityParameterByStrategyOneSide struct { Amount uint64 ActiveID int32 MaxActiveBinSlippage int32 StrategyParameters dlmmStrategyParameters } type dlmmAddLiquidityOneSideArgs struct { LiquidityParameter dlmmLiquidityOneSideParameter } type dlmmAddLiquidityByStrategyOneSideArgs struct { LiquidityParameter dlmmLiquidityParameterByStrategyOneSide } type dlmmCompressedBinDepositAmount struct { BinID int32 Amount uint32 } type dlmmAddLiquiditySingleSidePreciseParameter struct { Bins []dlmmCompressedBinDepositAmount DecompressMultiplier uint64 } type dlmmAddLiquiditySingleSidePreciseParameter2 struct { Bins []dlmmCompressedBinDepositAmount DecompressMultiplier uint64 MaxAmount uint64 } type dlmmAddLiquidityOneSidePreciseArgs struct { Parameter dlmmAddLiquiditySingleSidePreciseParameter } type dlmmAddLiquidityOneSidePrecise2Args struct { LiquidityParameter dlmmAddLiquiditySingleSidePreciseParameter2 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 dlmmInitializePositionArgs struct { LowerBinId int32 Width int32 } type dlmmInitializePositionByOperatorArgs struct { LowerBinId int32 Width int32 FeeOwner solana.PublicKey LockReleasePoint uint64 } 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 } type dlmmOneSideLiquidityAccounts struct { positionIdx int poolIdx int userTokenIdx int reserveIdx int tokenMintIdx int userIdx int tokenProgramIdx 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 meteoraInitializeCustomizablePermissionlessLbPairDiscriminator, meteoraInitializeCustomizablePermissionlessLbPair2Discriminator, meteoraInitializeLbPairDiscriminator, meteoraInitializeLbPair2Discriminator, meteoraInitializePermissionLbPairDiscriminator: return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset) case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator, meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator: return metaoradlmmPositionCreateParser(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, meteoraDlmmAddLiquidityByWeightDiscriminator, meteoraDlmmAddLiquidityOneSideDiscriminator, meteoraDlmmAddLiquidityOneSidePreciseDiscriminator, meteoraDlmmAddLiquidityOneSidePrecise2Discriminator, meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator: return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset) case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator: return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset) case meteoraDlmmRebalanceLiquidityDiscriminator: return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset) case meteoraDlmmRemoveAllLiquidityDiscriminator, meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator, meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator: return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset) case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator: return metaoradlmmPositionCloseParser(tx, instruction, innerInstructions, offset) default: return nil, increaseOffset(offset), InstructionIgnoredError } } type dlmmInitializeAccounts struct { pool solana.PublicKey token0 solana.PublicKey token1 solana.PublicKey baseTokenProgram solana.PublicKey quoteTokenProgram solana.PublicKey user solana.PublicKey } func resolveDlmmInitializeAccounts(result *RawTx, data []byte, accounts []int) (dlmmInitializeAccounts, error) { if len(data) < 8 { return dlmmInitializeAccounts{}, fmt.Errorf("instruction data too short") } accountList := result.getAccountList() resolveAt := func(position int) (solana.PublicKey, error) { if position < 0 || position >= len(accounts) { return solana.PublicKey{}, fmt.Errorf("accounts too short, missing position %d", position) } accountIndex := accounts[position] if accountIndex < 0 || accountIndex >= len(accountList) { return solana.PublicKey{}, fmt.Errorf("account index out of range at position %d", position) } return accountList[accountIndex], nil } resolveCommon := func(poolPos, token0Pos, token1Pos, userPos, baseTokenProgramPos, quoteTokenProgramPos int) (dlmmInitializeAccounts, error) { pool, err := resolveAt(poolPos) if err != nil { return dlmmInitializeAccounts{}, err } token0, err := resolveAt(token0Pos) if err != nil { return dlmmInitializeAccounts{}, err } token1, err := resolveAt(token1Pos) if err != nil { return dlmmInitializeAccounts{}, err } baseTokenProgram, err := resolveAt(baseTokenProgramPos) if err != nil { return dlmmInitializeAccounts{}, err } quoteTokenProgram, err := resolveAt(quoteTokenProgramPos) if err != nil { return dlmmInitializeAccounts{}, err } user, err := resolveAt(userPos) if err != nil { return dlmmInitializeAccounts{}, err } return dlmmInitializeAccounts{ pool: pool, token0: token0, token1: token1, baseTokenProgram: baseTokenProgram, quoteTokenProgram: quoteTokenProgram, user: user, }, nil } discriminator := *(*[8]byte)(data[:8]) switch discriminator { case meteoraInitializeLbPairDiscriminator, meteoraInitializeCustomizablePermissionlessLbPairDiscriminator: return resolveCommon(0, 2, 3, 8, 9, 9) case meteoraInitializeLbPair2Discriminator, meteoraInitializeCustomizablePermissionlessLbPair2Discriminator: return resolveCommon(0, 2, 3, 8, 11, 12) case meteoraInitializePermissionLbPairDiscriminator: return resolveCommon(1, 3, 4, 8, 11, 12) default: return dlmmInitializeAccounts{}, fmt.Errorf("unsupported initialize discriminator") } } func metaoradlmmInitializeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { accounts, err := resolveDlmmInitializeAccounts(tx.rawTx, instruction.Data, instruction.Accounts) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm initialize accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) } entryContract := tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] findMintDecimals := func(mint solana.PublicKey) uint8 { for _, acc := range tx.rawTx.Meta.PostTokenBalances { if acc.MintAccount.Equals(mint) { return uint8(acc.UITokenAmount.Decimals) } } return 0 } swap := Swap{ Program: SolProgramMeteoraDLMM, Event: "create", Pool: accounts.pool, BaseMint: accounts.token0, QuoteMint: accounts.token1, BaseTokenProgram: accounts.baseTokenProgram, QuoteTokenProgram: accounts.quoteTokenProgram, Creator: tx.rawTx.accountList[0], BaseMintDecimals: findMintDecimals(accounts.token0), QuoteMintDecimals: findMintDecimals(accounts.token1), User: accounts.user, EntryContract: entryContract, } createEvent, nextOffset, found, err := dlmmLbPairCreateEventFromInnerInstructions(innerInstructions, instruction, offset) if err != nil { return nil, nextOffset, err } if found { offset = nextOffset if !createEvent.LbPair.IsZero() { swap.Pool = createEvent.LbPair } if !createEvent.TokenX.IsZero() { swap.BaseMint = createEvent.TokenX } if !createEvent.TokenY.IsZero() { swap.QuoteMint = createEvent.TokenY } swap.BaseMintDecimals = findMintDecimals(swap.BaseMint) swap.QuoteMintDecimals = findMintDecimals(swap.QuoteMint) } return []Swap{swap}, offset, nil } func metaoradlmmPositionCreateParser(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] break } } } decode := instruction.Data if len(decode) < 8 { offset[1] += 1 return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position instruction data too short, offset, %d, %d", offset[0], offset[1]) } discriminator := *(*[8]byte)(decode[:8]) var ( lowerBinId int32 width int32 ) switch discriminator { case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator, meteoraDlmmInitializePositionPdaDiscriminator: var args dlmmInitializePositionArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } lowerBinId = args.LowerBinId width = args.Width case meteoraDlmmInitializePositionByOperatorDiscriminator: var args dlmmInitializePositionByOperatorArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position by operator decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } lowerBinId = args.LowerBinId width = args.Width default: return nil, increaseOffset(offset), InstructionIgnoredError } pool, positionAccount, eventUser, err := dlmmPositionCreateInstructionAccounts(result, discriminator, instruction.Accounts) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm create position accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) } createEvent, nextOffset, found, err := dlmmPositionCreateEventFromInnerInstructions(innerInstructions, instruction, offset) if err != nil { return nil, nextOffset, err } if !found { return nil, nextOffset, InstructionIgnoredError } offset = nextOffset if !createEvent.LbPair.IsZero() { pool = createEvent.LbPair } if !createEvent.Position.IsZero() { positionAccount = createEvent.Position } if !createEvent.Owner.IsZero() { eventUser = createEvent.Owner } swap := Swap{ Program: SolProgramMeteoraDLMM, Event: "open", Pool: pool, User: eventUser, EntryContract: entryContract, StartBinId: lowerBinId, EndBinId: dlmmPositionUpperBinId(lowerBinId, width), PositionAccount: positionAccount, } return []Swap{swap}, offset, nil } func metaoradlmmPositionCloseParser(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] break } } } decode := instruction.Data if len(decode) < 8 { offset[1] += 1 return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm close position instruction data too short, offset, %d, %d", offset[0], offset[1]) } discriminator := *(*[8]byte)(decode[:8]) pool, positionAccount, eventUser, err := dlmmPositionCloseInstructionAccounts(result, discriminator, instruction.Accounts) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm close position accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) } closeEvent, nextOffset, found, err := dlmmPositionCloseEventFromInnerInstructions(innerInstructions, instruction, offset) if err != nil { return nil, nextOffset, err } if !found { if discriminator == meteoraDlmmClosePositionIfEmptyDiscriminator { return nil, nextOffset, InstructionIgnoredError } return nil, nextOffset, fmt.Errorf("meteora dlmm close position event not found, offset, %d, %d", nextOffset[0], nextOffset[1]) } offset = nextOffset if !closeEvent.Position.IsZero() { positionAccount = closeEvent.Position } if !closeEvent.Owner.IsZero() { eventUser = closeEvent.Owner } swap := Swap{ Program: SolProgramMeteoraDLMM, Event: "close", Pool: pool, User: eventUser, EntryContract: entryContract, PositionAccount: positionAccount, } 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] break } } } 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]) var swapMode SwapMode var fixedAmount decimal.Decimal var limitAmount decimal.Decimal 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]) } swapMode = SwapModeExactIn fixedAmount = decimal.NewFromUint64(args.AmountIn) limitAmount = decimal.NewFromUint64(args.MinAmountOut) 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]) } swapMode = SwapModeExactOut fixedAmount = decimal.NewFromUint64(args.OutAmount) limitAmount = decimal.NewFromUint64(args.MaxInAmount) 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]) } swapMode = SwapModeExactIn fixedAmount = decimal.NewFromUint64(args.AmountIn) 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)) } } feeAmount, feeSide, feeMint, feeTokenProgram, feeDecimals := dlmmSwapFeeInfo( baseIsX, swapForY, swapEvent.Fee, baseMint, quoteMint, baseTokenProgram, quoteTokenProgram, baseDecimals, quoteDecimals, ) lpFeeAmount := dlmmSwapLpFeeAmount(swapEvent.Fee, swapEvent.ProtocolFee, swapEvent.HostFee) 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, FeeAmount: feeAmount, FeeBps: dlmmSwapFeeBpsString(swapEvent.FeeBps), LpFeeAmount: lpFeeAmount, FeeSide: feeSide, FeeMint: feeMint, FeeTokenProgram: feeTokenProgram, FeeMintDecimals: feeDecimals, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, ActiveBinId: swapEvent.EndBinId, StartBinId: swapEvent.StartBinId, EndBinId: swapEvent.EndBinId, } swap.SetSwapAmountInfo(swapMode, fixedAmount, limitAmount) 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 dlmmSwapFeeInfo( baseIsX bool, swapForY bool, fee uint64, baseMint solana.PublicKey, quoteMint solana.PublicKey, baseTokenProgram solana.PublicKey, quoteTokenProgram solana.PublicKey, baseDecimals uint8, quoteDecimals uint8, ) (decimal.Decimal, string, solana.PublicKey, solana.PublicKey, uint8) { feeAmount := decimal.NewFromUint64(fee) if baseIsX == swapForY { return feeAmount, "base", baseMint, baseTokenProgram, baseDecimals } return feeAmount, "quote", quoteMint, quoteTokenProgram, quoteDecimals } func dlmmSwapLpFeeAmount(fee, protocolFee, hostFee uint64) decimal.Decimal { total := decimal.NewFromUint64(fee) protocol := decimal.NewFromUint64(protocolFee) host := decimal.NewFromUint64(hostFee) lpFee := total.Sub(protocol).Sub(host) if lpFee.IsNegative() { return decimal.Zero } return lpFee } func dlmmSwapFeeBpsString(feeBps agbinary.Uint128) string { return feeBps.DecimalString() } 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] break } } } 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 weightDist []dlmmBinLiquidityDistributionByWeight startBinId int32 endBinId int32 oneSide 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) 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) 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 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 case meteoraDlmmAddLiquidityByWeightDiscriminator: var args dlmmAddLiquidityByWeightArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by weight decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } amountX = args.LiquidityParameter.AmountX amountY = args.LiquidityParameter.AmountY weightDist = args.LiquidityParameter.BinLiquidityDist startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist) case meteoraDlmmAddLiquidityOneSideDiscriminator: var args dlmmAddLiquidityOneSideArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } weightDist = args.LiquidityParameter.BinLiquidityDist startBinId, endBinId = dlmmMinMaxBinIdFromWeightDistribution(weightDist) oneSide = true case meteoraDlmmAddLiquidityOneSidePreciseDiscriminator: var args dlmmAddLiquidityOneSidePreciseArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.Parameter.Bins) oneSide = true case meteoraDlmmAddLiquidityOneSidePrecise2Discriminator: var args dlmmAddLiquidityOneSidePrecise2Args if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity one side precise2 decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } startBinId, endBinId = dlmmMinMaxBinIDFromCompressedDeposits(args.LiquidityParameter.Bins) oneSide = true case meteoraDlmmAddLiquidityByStrategyOneSideDiscriminator: var args dlmmAddLiquidityByStrategyOneSideArgs if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm add liquidity by strategy one side decode error: %v, offset, %d, %d", err, offset[0], offset[1]) } startBinId = args.LiquidityParameter.StrategyParameters.MinBinId endBinId = args.LiquidityParameter.StrategyParameters.MaxBinId oneSide = true default: return nil, increaseOffset(offset), InstructionIgnoredError } addEvent, nextOffset, err := dlmmAddLiquidityEventFromInnerInstructions(innerInstructions, instruction, offset) if err != nil { return nil, nextOffset, err } offset = nextOffset amountX = addEvent.Amounts[0] amountY = addEvent.Amounts[1] if oneSide { swaps, err := dlmmBuildOneSideAddSwap(tx, instruction, addEvent, startBinId, endBinId, entryContract) if err != nil { return nil, offset, err } return swaps, offset, nil } 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]) } 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", 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, ActiveBinId: addEvent.ActiveBinId, StartBinId: startBinId, EndBinId: endBinId, PositionAccount: result.accountList[accounts.positionIdx], } 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] break } } } 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 ( startBinId int32 endBinId int32 removeBp int32 ) switch discriminator { case meteoraDlmmRemoveAllLiquidityDiscriminator: removeBp = 10000 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]) } startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) removeBp = dlmmCommonRemoveBp(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]) } startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) removeBp = dlmmCommonRemoveBp(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 removeBp = int32(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 removeBp = int32(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", 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, ActiveBinId: removeEvent.ActiveBinId, StartBinId: startBinId, EndBinId: endBinId, RemoveBp: removeBp, PositionAccount: result.accountList[accounts.positionIdx], } return []Swap{swap}, offset, nil } func metaoradlmmClaimFeeParser(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] break } } } accounts, err := resolveDlmmClaimFeeAccounts(result, instruction.Data, instruction.Accounts) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) } claimEvent, nextOffset, err := dlmmClaimFeeEventFromInnerInstructions(innerInstructions, instruction, offset) if err != nil { return nil, nextOffset, err } offset = nextOffset if claimEvent.FeeX == 0 && claimEvent.FeeY == 0 { return nil, offset, InstructionIgnoredError } 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 baseAmount := decimal.NewFromUint64(claimEvent.FeeX) quoteAmount := decimal.NewFromUint64(claimEvent.FeeY) if !baseIsX { baseTokenProgram = tokenYProgram quoteTokenProgram = tokenXProgram baseReserveIdx = accounts.reserveYIdx quoteReserveIdx = accounts.reserveXIdx userBaseIdx = accounts.userTokenYIdx userQuoteIdx = accounts.userTokenXIdx baseAmount = decimal.NewFromUint64(claimEvent.FeeY) quoteAmount = decimal.NewFromUint64(claimEvent.FeeX) } eventUser := result.accountList[accounts.userIdx] if !claimEvent.Owner.IsZero() { eventUser = claimEvent.Owner } 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: "claim_fee", 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, PositionAccount: result.accountList[accounts.positionIdx], } if claimEvent.HasActiveBin { swap.ActiveBinId = claimEvent.ActiveBinId swap.StartBinId = claimEvent.ActiveBinId swap.EndBinId = claimEvent.ActiveBinId } return []Swap{swap}, offset, nil } func metaoradlmmRebalanceLiquidityParser(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] break } } } accounts, err := resolveDlmmRebalanceAccounts(result, instruction.Accounts) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity accounts parse error: %v, offset, %d, %d", err, offset[0], offset[1]) } event, nextOffset, err := dlmmRebalancingEventFromInnerInstructions(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 withdrawBase := event.XWithdrawnAmount withdrawQuote := event.YWithdrawnAmount addBase := event.XAddedAmount addQuote := event.YAddedAmount if !baseIsX { baseTokenProgram = tokenYProgram quoteTokenProgram = tokenXProgram baseReserveIdx = accounts.reserveYIdx quoteReserveIdx = accounts.reserveXIdx userBaseIdx = accounts.userTokenYIdx userQuoteIdx = accounts.userTokenXIdx withdrawBase = event.YWithdrawnAmount withdrawQuote = event.XWithdrawnAmount addBase = event.YAddedAmount addQuote = event.XAddedAmount } eventUser := result.accountList[accounts.userIdx] if !event.Owner.IsZero() { eventUser = event.Owner } 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)) } } var swaps []Swap if withdrawBase > 0 || withdrawQuote > 0 { swaps = append(swaps, Swap{ Program: SolProgramMeteoraDLMM, Event: "remove", Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseTokenProgram, QuoteTokenProgram: quoteTokenProgram, BaseMintDecimals: baseDecimals, QuoteMintDecimals: quoteDecimals, User: eventUser, BaseAmount: decimal.NewFromUint64(withdrawBase), QuoteAmount: decimal.NewFromUint64(withdrawQuote), BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, ActiveBinId: event.ActiveBinId, StartBinId: event.OldMinBinId, EndBinId: event.OldMaxBinId, PositionAccount: result.accountList[accounts.positionIdx], }) } if addBase > 0 || addQuote > 0 { swaps = append(swaps, Swap{ Program: SolProgramMeteoraDLMM, Event: "add", Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseTokenProgram, QuoteTokenProgram: quoteTokenProgram, BaseMintDecimals: baseDecimals, QuoteMintDecimals: quoteDecimals, User: eventUser, BaseAmount: decimal.NewFromUint64(addBase), QuoteAmount: decimal.NewFromUint64(addQuote), BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, EntryContract: entryContract, ActiveBinId: event.ActiveBinId, StartBinId: event.NewMinBinId, EndBinId: event.NewMaxBinId, PositionAccount: result.accountList[accounts.positionIdx], }) } if len(swaps) == 0 { return nil, offset, InstructionIgnoredError } return swaps, 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 dlmmClaimFeeEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmClaimFeeInnerEvent, [2]uint, error) { var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return dlmmClaimFeeInnerEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee get inner instructions error: %v, offset, %d, %d", err, offset[0], prefixLen) } var ( found bool event dlmmClaimFeeInnerEvent matchedIdx int ) for innerIndex, innerInstr := range inners { if innerInstr.ProgramIDIndex != instruction.ProgramIDIndex { continue } decoded, ok := dlmmDecodeClaimFeeEvent(innerInstr.Data) if !ok { continue } found = true event = decoded matchedIdx = innerIndex if decoded.HasActiveBin { break } } if !found { return dlmmClaimFeeInnerEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm claim fee event not found, offset, %d, %d", offset[0], prefixLen) } if offset[1] == 0 { offset[0] += 1 } else { offset[1] = uint(matchedIdx) + 1 + prefixLen } return event, offset, nil } func dlmmRebalancingEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmRebalancingEvent, [2]uint, error) { var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return dlmmRebalancingEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance 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 := dlmmDecodeRebalancingEvent(innerInstr.Data) if !ok { continue } if offset[1] == 0 { offset[0] += 1 } else { offset[1] = uint(innerIndex) + 1 + prefixLen } return event, offset, nil } return dlmmRebalancingEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm rebalance liquidity event not found, offset, %d, %d", offset[0], prefixLen) } func dlmmPositionCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCreateEvent, [2]uint, bool, error) { var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return dlmmPositionCreateEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm create position 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 := dlmmDecodePositionCreateEvent(innerInstr.Data) if !ok { continue } if offset[1] == 0 { offset[0] += 1 } else { offset[1] = uint(innerIndex) + 1 + prefixLen } return event, offset, true, nil } return dlmmPositionCreateEvent{}, increaseOffset(offset), false, nil } func dlmmPositionCloseEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCloseEvent, [2]uint, bool, error) { var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return dlmmPositionCloseEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm close position 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 := dlmmDecodePositionCloseEvent(innerInstr.Data) if !ok { continue } if offset[1] == 0 { offset[0] += 1 } else { offset[1] = uint(innerIndex) + 1 + prefixLen } return event, offset, true, nil } return dlmmPositionCloseEvent{}, increaseOffset(offset), false, nil } func dlmmLbPairCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmLbPairCreateEvent, [2]uint, bool, error) { var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, fmt.Errorf("meteora dlmm create 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 := dlmmDecodeLbPairCreateEvent(innerInstr.Data) if !ok { continue } if offset[1] == 0 { offset[0] += 1 } else { offset[1] = uint(innerIndex) + 1 + prefixLen } return event, offset, true, nil } return dlmmLbPairCreateEvent{}, increaseOffset(offset), false, nil } func dlmmDecodeLbPairCreateEvent(data []byte) (dlmmLbPairCreateEvent, bool) { switch { case len(data) >= 8 && bytes.Equal(data[:8], meteoraInitializeLbPairEventDiscriminator[:]): var event dlmmLbPairCreateEvent if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil { return dlmmLbPairCreateEvent{}, false } return event, true case len(data) >= 16 && bytes.Equal(data[:8], eventDiscriminator[:]) && bytes.Equal(data[8:16], meteoraInitializeLbPairEventDiscriminator[:]): var event dlmmLbPairCreateEvent if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { return dlmmLbPairCreateEvent{}, false } return event, true default: return dlmmLbPairCreateEvent{}, false } } 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 dlmmDecodePositionCreateEvent(data []byte) (dlmmPositionCreateEvent, bool) { switch { case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmPositionCreateEventDiscriminator[:]): var event dlmmPositionCreateEvent if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil { return dlmmPositionCreateEvent{}, false } return event, true case len(data) >= 16 && bytes.Equal(data[:8], eventDiscriminator[:]) && bytes.Equal(data[8:16], meteoraDlmmPositionCreateEventDiscriminator[:]): var event dlmmPositionCreateEvent if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { return dlmmPositionCreateEvent{}, false } return event, true default: return dlmmPositionCreateEvent{}, false } } func dlmmDecodePositionCloseEvent(data []byte) (dlmmPositionCloseEvent, bool) { switch { case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmPositionCloseEventDiscriminator[:]): var event dlmmPositionCloseEvent if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil { return dlmmPositionCloseEvent{}, false } return event, true case len(data) >= 16 && bytes.Equal(data[:8], eventDiscriminator[:]) && bytes.Equal(data[8:16], meteoraDlmmPositionCloseEventDiscriminator[:]): var event dlmmPositionCloseEvent if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { return dlmmPositionCloseEvent{}, false } return event, true default: return dlmmPositionCloseEvent{}, false } } func dlmmDecodeClaimFeeEvent(data []byte) (dlmmClaimFeeInnerEvent, bool) { switch { case len(data) >= 16 && bytes.Equal(data[:8], eventDiscriminator[:]) && bytes.Equal(data[8:16], meteoraDlmmClaimFee2EventDiscriminator[:]): var event dlmmClaimFee2Event if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { return dlmmClaimFeeInnerEvent{}, false } return dlmmClaimFeeInnerEvent{ LbPair: event.LbPair, Position: event.Position, Owner: event.Owner, FeeX: event.FeeX, FeeY: event.FeeY, ActiveBinId: event.ActiveBinId, HasActiveBin: true, }, true case len(data) >= 16 && bytes.Equal(data[:8], eventDiscriminator[:]) && bytes.Equal(data[8:16], meteoraDlmmClaimFeeEventDiscriminator[:]): var event dlmmClaimFeeEvent if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { return dlmmClaimFeeInnerEvent{}, false } return dlmmClaimFeeInnerEvent{ LbPair: event.LbPair, Position: event.Position, Owner: event.Owner, FeeX: event.FeeX, FeeY: event.FeeY, }, true default: return dlmmClaimFeeInnerEvent{}, false } } func dlmmDecodeRebalancingEvent(data []byte) (dlmmRebalancingEvent, bool) { switch { case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmRebalancingEventDiscriminator[:]): var event dlmmRebalancingEvent if err := agbinary.NewBorshDecoder(data[8:]).Decode(&event); err != nil { return dlmmRebalancingEvent{}, false } return event, true case len(data) >= 16 && bytes.Equal(data[:8], eventDiscriminator[:]) && bytes.Equal(data[8:16], meteoraDlmmRebalancingEventDiscriminator[:]): var event dlmmRebalancingEvent if err := agbinary.NewBorshDecoder(data[16:]).Decode(&event); err != nil { return dlmmRebalancingEvent{}, false } return event, true default: return dlmmRebalancingEvent{}, false } } func dlmmPositionCreateInstructionAccounts(result *RawTx, discriminator [8]byte, accounts []int) (pool, position, owner solana.PublicKey, err error) { switch discriminator { case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator: if len(accounts) < 4 { return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 4") } return result.accountList[accounts[2]], result.accountList[accounts[1]], result.accountList[accounts[3]], nil case meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator: if len(accounts) < 5 { return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 5") } return result.accountList[accounts[3]], result.accountList[accounts[2]], result.accountList[accounts[4]], nil default: return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("unsupported create position discriminator") } } func dlmmPositionCloseInstructionAccounts(result *RawTx, discriminator [8]byte, accounts []int) (pool, position, owner solana.PublicKey, err error) { switch discriminator { case meteoraDlmmClosePositionDiscriminator: if len(accounts) < 5 { return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 5") } return result.accountList[accounts[1]], result.accountList[accounts[0]], result.accountList[accounts[4]], nil case meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator: if len(accounts) < 2 { return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short, expected at least 2") } return solana.PublicKey{}, result.accountList[accounts[0]], result.accountList[accounts[1]], nil default: return solana.PublicKey{}, solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("unsupported close position discriminator") } } 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 resolveDlmmOneSideLiquidityAccounts(result *RawTx, accounts []int) (dlmmOneSideLiquidityAccounts, error) { if len(accounts) < 10 { return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 10") } 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 dlmmOneSideLiquidityAccounts{}, fmt.Errorf("event authority not found") } if eventAuthorityPos+1 >= len(accounts) || !accountList[accounts[eventAuthorityPos+1]].Equals(meteoraDlmmProgram) { return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("program id not found after event authority") } tokenProgramPos := eventAuthorityPos - 1 userPos := eventAuthorityPos - 2 if tokenProgramPos < 0 || userPos < 0 { return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("one side liquidity account positions invalid") } if len(accounts) < 6 { return dlmmOneSideLiquidityAccounts{}, fmt.Errorf("accounts too short for one side liquidity parsing") } return dlmmOneSideLiquidityAccounts{ positionIdx: accounts[0], poolIdx: accounts[1], userTokenIdx: accounts[3], reserveIdx: accounts[4], tokenMintIdx: accounts[5], userIdx: accounts[userPos], tokenProgramIdx: accounts[tokenProgramPos], }, nil } func dlmmBuildOneSideAddSwap( tx *Tx, instruction Instruction, addEvent dlmmAddLiquidityEvent, startBinId int32, endBinId int32, entryContract solana.PublicKey, ) ([]Swap, error) { result := tx.rawTx accounts, err := resolveDlmmOneSideLiquidityAccounts(result, instruction.Accounts) if err != nil { return nil, err } knownMint := result.accountList[accounts.tokenMintIdx] knownTokenProgram := result.accountList[accounts.tokenProgramIdx] knownDecimals, ok := dlmmTokenDecimals(result, accounts.reserveIdx) if !ok { knownDecimals, _ = dlmmTokenDecimals(result, accounts.userTokenIdx) } knownReserveBalance := getAccountBalanceAfterTx(result, accounts.reserveIdx) knownUserBalance := getAccountBalanceAfterTx(result, accounts.userTokenIdx) if knownMint.Equals(wSolMint) { if solAmount, err := GetSolAfterTx(result, accounts.userIdx); err == nil { knownUserBalance = knownUserBalance.Add(decimal.NewFromUint64(solAmount)) } } eventUser := result.accountList[accounts.userIdx] if !addEvent.From.IsZero() { eventUser = addEvent.From } positionAccount := result.accountList[accounts.positionIdx] if !addEvent.Position.IsZero() { positionAccount = addEvent.Position } swap := Swap{ Program: SolProgramMeteoraDLMM, Event: "add", Pool: result.accountList[accounts.poolIdx], User: eventUser, EntryContract: entryContract, ActiveBinId: addEvent.ActiveBinId, StartBinId: startBinId, EndBinId: endBinId, PositionAccount: positionAccount, } knownIsX := dlmmInferOneSideLiquidityAxis(result, accounts, addEvent) if knownIsX { swap.BaseMint = knownMint swap.BaseTokenProgram = knownTokenProgram swap.BaseMintDecimals = knownDecimals swap.BaseAmount = decimal.NewFromUint64(addEvent.Amounts[0]) swap.BaseReserve = knownReserveBalance swap.UserBaseBalance = knownUserBalance if _, exists := tx.Token[knownMint]; !exists && !knownMint.Equals(wSolMint) { tx.Token[knownMint] = TokenMeta{ Mint: knownMint, Decimals: knownDecimals, TokenProgram: knownTokenProgram, } } } else { swap.QuoteMint = knownMint swap.QuoteTokenProgram = knownTokenProgram swap.QuoteMintDecimals = knownDecimals swap.QuoteAmount = decimal.NewFromUint64(addEvent.Amounts[1]) swap.QuoteReserve = knownReserveBalance swap.UserQuoteBalance = knownUserBalance if _, exists := tx.Token[knownMint]; !exists && !knownMint.Equals(wSolMint) { tx.Token[knownMint] = TokenMeta{ Mint: knownMint, Decimals: knownDecimals, TokenProgram: knownTokenProgram, } } } return []Swap{swap}, nil } func dlmmInferOneSideLiquidityAxis(result *RawTx, accounts dlmmOneSideLiquidityAccounts, addEvent dlmmAddLiquidityEvent) bool { knownAmount, ok := dlmmTokenDelta(result, accounts.reserveIdx) if !ok || knownAmount.IsZero() { knownAmount, _ = dlmmTokenDelta(result, accounts.userTokenIdx) } amountX := decimal.NewFromUint64(addEvent.Amounts[0]) amountY := decimal.NewFromUint64(addEvent.Amounts[1]) switch { case !knownAmount.IsZero() && knownAmount.Equal(amountX) && !knownAmount.Equal(amountY): return true case !knownAmount.IsZero() && knownAmount.Equal(amountY) && !knownAmount.Equal(amountX): return false case addEvent.Amounts[0] > 0 && addEvent.Amounts[1] == 0: return true case addEvent.Amounts[1] > 0 && addEvent.Amounts[0] == 0: return false default: return true } } func resolveDlmmClaimFeeAccounts(result *RawTx, data []byte, accounts []int) (dlmmLiquidityAccounts, error) { if len(data) < 8 { return dlmmLiquidityAccounts{}, fmt.Errorf("instruction data too short") } discriminator := *(*[8]byte)(data[:8]) accountList := result.accountList switch discriminator { case meteoraDlmmClaimFee2Discriminator: if len(accounts) < 14 { return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 14") } if !accountList[accounts[12]].Equals(meteoraDlmmEventAuthority) { return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch") } if !accountList[accounts[13]].Equals(meteoraDlmmProgram) { return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch") } return dlmmLiquidityAccounts{ positionIdx: accounts[1], poolIdx: accounts[0], userTokenXIdx: accounts[5], userTokenYIdx: accounts[6], reserveXIdx: accounts[3], reserveYIdx: accounts[4], tokenXMintIdx: accounts[7], tokenYMintIdx: accounts[8], userIdx: accounts[2], tokenXProgramIdx: accounts[9], tokenYProgramIdx: accounts[10], }, nil case meteoraDlmmClaimFeeDiscriminator: if len(accounts) < 14 { return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 14") } if !accountList[accounts[12]].Equals(meteoraDlmmEventAuthority) { return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch") } if !accountList[accounts[13]].Equals(meteoraDlmmProgram) { return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch") } return dlmmLiquidityAccounts{ positionIdx: accounts[1], poolIdx: accounts[0], userTokenXIdx: accounts[7], userTokenYIdx: accounts[8], reserveXIdx: accounts[5], reserveYIdx: accounts[6], tokenXMintIdx: accounts[9], tokenYMintIdx: accounts[10], userIdx: accounts[4], tokenXProgramIdx: accounts[11], tokenYProgramIdx: accounts[11], }, nil default: return dlmmLiquidityAccounts{}, fmt.Errorf("unsupported claim fee discriminator") } } func resolveDlmmRebalanceAccounts(result *RawTx, accounts []int) (dlmmLiquidityAccounts, error) { if len(accounts) < 17 { return dlmmLiquidityAccounts{}, fmt.Errorf("accounts too short, expected at least 17") } accountList := result.accountList if !accountList[accounts[15]].Equals(meteoraDlmmEventAuthority) { return dlmmLiquidityAccounts{}, fmt.Errorf("event authority mismatch") } if !accountList[accounts[16]].Equals(meteoraDlmmProgram) { return dlmmLiquidityAccounts{}, fmt.Errorf("program id mismatch") } 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[9], tokenXProgramIdx: accounts[11], tokenYProgramIdx: accounts[12], }, 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 dlmmAllocateByWeights(total uint64, weights []uint64) []decimal.Decimal { if len(weights) == 0 { return nil } sumWeights := uint64(0) for _, weight := range weights { sumWeights += weight } if sumWeights == 0 { sumWeights = uint64(len(weights)) weights = append([]uint64(nil), weights...) for i := range weights { weights[i] = 1 } } allocations := make([]decimal.Decimal, len(weights)) remaining := total for i, weight := range weights { amount := uint64(0) if i == len(weights)-1 { amount = remaining } else if sumWeights > 0 { amount = total * weight / sumWeights if amount > remaining { amount = remaining } remaining -= amount } allocations[i] = decimal.NewFromUint64(amount) } return allocations } func dlmmApplySignedAllocation(values []decimal.Decimal, negative bool) []decimal.Decimal { if !negative { return values } out := make([]decimal.Decimal, len(values)) for i, value := range values { out[i] = value.Neg() } return out } func dlmmMinMaxBinIDFromCompressedDeposits(bins []dlmmCompressedBinDepositAmount) (startBinID, endBinID int32) { if len(bins) == 0 { return 0, 0 } startBinID = bins[0].BinID endBinID = bins[0].BinID for _, bin := range bins[1:] { if bin.BinID < startBinID { startBinID = bin.BinID } if bin.BinID > endBinID { endBinID = bin.BinID } } return startBinID, endBinID } func dlmmCommonRemoveBp(reduction []dlmmBinLiquidityReduction) int32 { if len(reduction) == 0 { return 0 } bpsToRemove := reduction[0].BpsToRemove for _, item := range reduction[1:] { if item.BpsToRemove != bpsToRemove { return 0 } } return int32(bpsToRemove) } func dlmmPositionUpperBinId(lowerBinId, width int32) int32 { if width <= 0 { return lowerBinId } return lowerBinId + width - 1 } 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 dlmmMinMaxBinIdFromWeightDistribution(dist []dlmmBinLiquidityDistributionByWeight) (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 }