Files
pump-parser/metaoradlmm.go

2066 lines
69 KiB
Go
Raw Normal View History

2026-01-07 16:41:49 +08:00
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
}
2026-01-15 17:44:39 +08:00
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
}
2026-03-19 14:10:14 +08:00
type dlmmPositionCreateEvent struct {
LbPair solana.PublicKey
Position solana.PublicKey
Owner solana.PublicKey
}
type dlmmPositionCloseEvent struct {
Position solana.PublicKey
Owner 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
}
2026-01-15 17:44:39 +08:00
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 {
2026-01-15 17:45:17 +08:00
AmountX uint64
AmountY uint64
ActiveID int32
2026-01-15 17:44:39 +08:00
MaxActiveBinSlippage int32
2026-01-15 17:45:17 +08:00
StrategyParameters dlmmStrategyParameters
2026-01-15 17:44:39 +08:00
}
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 {
2026-01-15 17:45:17 +08:00
BinLiquidityRemoval []dlmmBinLiquidityReduction
2026-01-15 17:44:39 +08:00
RemainingAccountsInfo dlmmRemainingAccountsInfo
}
type dlmmRemoveLiquidityByRangeArgs struct {
2026-01-15 17:45:17 +08:00
FromBinId int32
ToBinId int32
BpsToRemove uint16
2026-01-15 17:44:39 +08:00
}
type dlmmRemoveLiquidityByRange2Args struct {
2026-01-15 17:45:17 +08:00
FromBinId int32
ToBinId int32
BpsToRemove uint16
2026-01-15 17:44:39 +08:00
RemainingAccountsInfo dlmmRemainingAccountsInfo
}
2026-03-19 14:10:14 +08:00
type dlmmInitializePositionArgs struct {
LowerBinId int32
Width int32
}
type dlmmInitializePositionByOperatorArgs struct {
LowerBinId int32
Width int32
FeeOwner solana.PublicKey
LockReleasePoint uint64
}
2026-01-15 17:44:39 +08:00
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
}
2026-01-07 16:41:49 +08:00
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
}
2026-01-15 17:44:39 +08:00
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
}
2026-01-07 16:41:49 +08:00
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 {
2026-02-02 17:59:47 +08:00
case meteoraInitializeLbPairDiscriminator:
return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset)
2026-03-19 14:10:14 +08:00
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator,
meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator:
return metaoradlmmPositionCreateParser(tx, instruction, innerInstructions, offset)
2026-01-07 16:41:49 +08:00
case meteoraDlmmSwapDiscriminator, meteoraDlmmSwapExactOutDiscriminator, meteoraDlmmSwapWithPriceImpactDiscriminator:
return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmSwap2Discriminator, meteoraDlmmSwapExactOut2Discriminator, meteoraDlmmSwapWithPriceImpact2Discriminator:
return metaoradlmmSwap2Parser(tx, instruction, innerInstructions, offset)
2026-01-15 17:44:39 +08:00
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator:
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator:
return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmRebalanceLiquidityDiscriminator:
return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset)
2026-01-15 17:44:39 +08:00
case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
2026-03-19 14:10:14 +08:00
case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator:
return metaoradlmmPositionCloseParser(tx, instruction, innerInstructions, offset)
2026-01-07 16:41:49 +08:00
default:
return nil, increaseOffset(offset), InstructionIgnoredError
}
}
2026-02-02 17:59:47 +08:00
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 {
2026-02-09 14:46:19 +08:00
if innerInstr.ProgramIDIndex == programIndex && len(innerInstr.Data) >= 16 && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], meteoraInitializeLbPairEventDiscriminator[:]) {
2026-02-02 17:59:47 +08:00
if offset[1] == 0 {
offset[0] += 1
} else {
2026-02-09 14:46:19 +08:00
offset[1] = uint(innerIndex) + 1 + prefixLen
2026-02-02 17:59:47 +08:00
}
break
}
}
return []Swap{swap}, offset, nil
}
2026-03-19 14:10:14 +08:00
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, err := dlmmPositionCreateEventFromInnerInstructions(innerInstructions, instruction, offset)
if err != nil {
return nil, nextOffset, err
}
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
}
2026-01-07 16:41:49 +08:00
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]
2026-02-09 16:09:31 +08:00
break
2026-01-07 16:41:49 +08:00
}
}
}
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]
2026-02-09 14:46:19 +08:00
var prefixLen = offset[1]
var swapEvent dlmmSwapEvent
inners, err := getInnerInstructions(innerInstructions, prefixLen)
2026-01-07 16:41:49 +08:00
if err != nil {
2026-02-09 14:46:19 +08:00
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
2026-01-07 16:41:49 +08:00
}
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,
2026-03-18 14:38:25 +08:00
ActiveBinId: swapEvent.EndBinId,
StartBinId: swapEvent.StartBinId,
EndBinId: swapEvent.EndBinId,
2026-01-07 16:41:49 +08:00
}
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)
}
2026-01-15 17:44:39 +08:00
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]
2026-02-09 16:09:31 +08:00
break
2026-01-15 17:44:39 +08:00
}
}
}
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 (
2026-01-15 17:45:17 +08:00
amountX uint64
amountY uint64
binDist []dlmmBinLiquidityDistribution
2026-01-15 17:44:39 +08:00
startBinId int32
endBinId int32
2026-01-15 17:45:17 +08:00
hasRange bool
2026-01-15 17:44:39 +08:00
)
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,
2026-03-16 10:14:50 +08:00
Event: "add",
2026-01-15 17:44:39 +08:00
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,
2026-03-18 14:38:25 +08:00
ActiveBinId: addEvent.ActiveBinId,
2026-01-15 17:44:39 +08:00
StartBinId: startBinId,
EndBinId: endBinId,
BinChanges: binChanges,
2026-03-19 14:10:14 +08:00
PositionAccount: result.accountList[accounts.positionIdx],
2026-01-15 17:44:39 +08:00
}
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]
2026-02-09 16:09:31 +08:00
break
2026-01-15 17:44:39 +08:00
}
}
}
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
2026-03-18 14:38:25 +08:00
removeBp int32
2026-01-15 17:44:39 +08:00
)
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)
2026-03-18 14:38:25 +08:00
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
2026-01-15 17:44:39 +08:00
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)
2026-03-18 14:38:25 +08:00
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
2026-01-15 17:44:39 +08:00
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
2026-03-18 14:38:25 +08:00
removeBp = int32(args.BpsToRemove)
2026-01-15 17:44:39 +08:00
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
2026-03-18 14:38:25 +08:00
removeBp = int32(args.BpsToRemove)
2026-01-15 17:44:39 +08:00
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,
2026-03-16 10:14:50 +08:00
Event: "remove",
2026-01-15 17:44:39 +08:00
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,
2026-03-18 14:38:25 +08:00
ActiveBinId: removeEvent.ActiveBinId,
2026-01-15 17:44:39 +08:00
StartBinId: startBinId,
EndBinId: endBinId,
2026-03-18 14:38:25 +08:00
RemoveBp: removeBp,
2026-01-15 17:44:39 +08:00
BinChanges: binChanges,
2026-03-19 14:10:14 +08:00
PositionAccount: result.accountList[accounts.positionIdx],
2026-01-15 17:44:39 +08:00
}
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,
2026-03-19 14:10:14 +08:00
PositionAccount: result.accountList[accounts.positionIdx],
}
if claimEvent.HasActiveBin {
2026-03-18 14:38:25 +08:00
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,
2026-03-18 14:38:25 +08:00
ActiveBinId: event.ActiveBinId,
StartBinId: event.OldMinBinId,
EndBinId: event.OldMaxBinId,
BinChanges: dlmmBinChangesFromRange(event.OldMinBinId, event.OldMaxBinId, 0),
2026-03-19 14:10:14 +08:00
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,
2026-03-18 14:38:25 +08:00
ActiveBinId: event.ActiveBinId,
StartBinId: event.NewMinBinId,
EndBinId: event.NewMaxBinId,
BinChanges: dlmmBinChangesFromRange(event.NewMinBinId, event.NewMaxBinId, 0),
2026-03-19 14:10:14 +08:00
PositionAccount: result.accountList[accounts.positionIdx],
})
}
if len(swaps) == 0 {
return nil, offset, InstructionIgnoredError
}
return swaps, offset, nil
}
2026-01-07 16:41:49 +08:00
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
}
2026-01-15 17:44:39 +08:00
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 {
2026-02-09 14:46:19 +08:00
offset[1] = uint(innerIndex) + 1 + prefixLen
2026-01-15 17:44:39 +08:00
}
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 {
2026-02-09 14:46:19 +08:00
offset[1] = uint(innerIndex) + 1 + prefixLen
2026-01-15 17:44:39 +08:00
}
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)
}
2026-03-19 14:10:14 +08:00
func dlmmPositionCreateEventFromInnerInstructions(innerInstructions InnerInstructions, instruction Instruction, offset [2]uint) (dlmmPositionCreateEvent, [2]uint, error) {
var prefixLen = offset[1]
inners, err := getInnerInstructions(innerInstructions, prefixLen)
if err != nil {
return dlmmPositionCreateEvent{}, increaseOffset(offset), 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, nil
}
return dlmmPositionCreateEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm create position event not found, offset, %d, %d", offset[0], prefixLen)
}
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
}
2026-01-15 17:44:39 +08:00
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
}
}
2026-03-19 14:10:14 +08:00
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
}
}
2026-03-19 14:10:14 +08:00
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")
}
}
2026-01-07 16:41:49 +08:00
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")
}
2026-01-15 17:44:39 +08:00
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 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
}
2026-01-07 16:41:49 +08:00
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
}
2026-01-15 17:44:39 +08:00
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
}
2026-03-18 14:38:25 +08:00
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)
}
2026-03-19 14:10:14 +08:00
func dlmmPositionUpperBinId(lowerBinId, width int32) int32 {
if width <= 0 {
return lowerBinId
}
return lowerBinId + width - 1
}
2026-01-15 17:44:39 +08:00
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
}