Compare commits

..

11 Commits

Author SHA1 Message Date
bijianing97
401dca225a Add dlmm open and close 2026-03-19 14:10:14 +08:00
bijianing97
db8c8727f4 Add dlmm start and end bin 2026-03-18 14:38:25 +08:00
bijianing97
09de6ba649 Add dlmm claim fee and update dlmm add and remove 2026-03-16 11:37:19 +08:00
bijianing97
7a82990770 Update meteoradlmm remove and add enum 2026-03-16 10:14:50 +08:00
bijianing97
e82bcb3c07 Merge remote-tracking branch 'origin' 2026-03-12 16:22:09 +08:00
bijianing97
a74f769064 Add raydiumv4 swapv2 2026-03-12 16:21:38 +08:00
thloyi
1e276e8bd2 [tx] swap inner idx 2026-03-12 13:49:34 +08:00
thloyi
eb2bde98ac update parsed tx: add swap at instr idx 2026-03-12 13:49:34 +08:00
66f0d247f5 chore: add astralane fee address 2026-03-12 12:08:06 +08:00
879b7fefad chore: add PlatformDexScreener 2026-03-12 11:52:58 +08:00
149dfae378 chore: add trojan fee address 2026-03-10 11:23:15 +08:00
7 changed files with 1010 additions and 26 deletions

View File

@@ -41,6 +41,12 @@ var platformFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco"): PlatformTrojan, solana.MustPublicKeyFromBase58("9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco"): PlatformTrojan,
solana.MustPublicKeyFromBase58("92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH"): PlatformTrojan, solana.MustPublicKeyFromBase58("92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH"): PlatformTrojan,
solana.MustPublicKeyFromBase58("65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH"): PlatformTrojan, solana.MustPublicKeyFromBase58("65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH"): PlatformTrojan,
solana.MustPublicKeyFromBase58("8jgg7moFJkHyTtAv9M6RBSPMp2oXeXhuiUMKW8YbYCWn"): PlatformTrojan,
solana.MustPublicKeyFromBase58("BJgbYMZgqm79gNrmm31tV3L8GQorw91XFm4m7evyfPjr"): PlatformTrojan,
solana.MustPublicKeyFromBase58("BWgb8wR1FEGiu1jCDSKuHKf752W27b4iN6SvoNCiK4qp"): PlatformTrojan,
solana.MustPublicKeyFromBase58("GV4Bt6ehW5x5dqtaWAJBSnz8uum5Z2Rp9P2Tr5iVuQn5"): PlatformTrojan,
solana.MustPublicKeyFromBase58("2jwHNxavSoMZMEDbT1eV9PcPt5dDcayCqM6MkgaPpmWQ"): PlatformTrojan,
solana.MustPublicKeyFromBase58("66N1M2aaDSdJFZ1d7YoVN4EU45ju6XiscapLVHn5FLms"): PlatformTrojan,
solana.MustPublicKeyFromBase58("4mih95RmBqfHYvEfqq6uGGLp1Fr3gVS3VNSEa3JVRfQK"): PlatformRaybot, solana.MustPublicKeyFromBase58("4mih95RmBqfHYvEfqq6uGGLp1Fr3gVS3VNSEa3JVRfQK"): PlatformRaybot,
solana.MustPublicKeyFromBase58("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"): PlatformMoonshot, solana.MustPublicKeyFromBase58("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"): PlatformMoonshot,
solana.MustPublicKeyFromBase58("3kxSQybWEeQZsMuNWMRJH4TxrhwoDwfv41TNMLRzFP5A"): PlatformMEVX, solana.MustPublicKeyFromBase58("3kxSQybWEeQZsMuNWMRJH4TxrhwoDwfv41TNMLRzFP5A"): PlatformMEVX,
@@ -61,6 +67,7 @@ var platformFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("HkJYryz2BNeMQfuuSWDYktWt5fZLV26eK6nqu7EJycoG"): PlatformAxiom, solana.MustPublicKeyFromBase58("HkJYryz2BNeMQfuuSWDYktWt5fZLV26eK6nqu7EJycoG"): PlatformAxiom,
solana.MustPublicKeyFromBase58("BfFX9rUm8qTZiZjmeq9BktWVTNuG3YWMc5AvkrCKJike"): PlatformAxiom, solana.MustPublicKeyFromBase58("BfFX9rUm8qTZiZjmeq9BktWVTNuG3YWMc5AvkrCKJike"): PlatformAxiom,
solana.MustPublicKeyFromBase58("2ApLdwLrGayEmxgpLX9BTR47Q2QprfMg5SpjrLeaK8s7"): PlatformAxiom, solana.MustPublicKeyFromBase58("2ApLdwLrGayEmxgpLX9BTR47Q2QprfMg5SpjrLeaK8s7"): PlatformAxiom,
solana.MustPublicKeyFromBase58("AVahywMVNRYzdgWrufSWrtdGXAeNEvfpJFxhVFK516mT"): PlatformDexScreener,
} }
var mevAgentFeeAddresses = map[solana.PublicKey]string{ var mevAgentFeeAddresses = map[solana.PublicKey]string{
@@ -190,6 +197,7 @@ var mevAgentFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes"): MevAgentAstralane, solana.MustPublicKeyFromBase58("ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES"): MevAgentAstralane, solana.MustPublicKeyFromBase58("ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes"): MevAgentAstralane, solana.MustPublicKeyFromBase58("ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("astraubkDw81n4LuutzSQ8uzHCv4BhPVhfvTcYv8SKC"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK"): MevAgentAstralane, solana.MustPublicKeyFromBase58("astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK"): MevAgentAstralane, solana.MustPublicKeyFromBase58("B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK"): MevAgentAstralane,
solana.MustPublicKeyFromBase58("B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh"): MevAgentAstralane, solana.MustPublicKeyFromBase58("B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh"): MevAgentAstralane,

View File

@@ -75,6 +75,7 @@ const (
PlatformMaestro = "maestro" PlatformMaestro = "maestro"
PlatformBonkBot = "bonkbot" PlatformBonkBot = "bonkbot"
PlatformPadre = "padre" PlatformPadre = "padre"
PlatformDexScreener = "dexscreener"
// used to flag transactions impersonating platform users // used to flag transactions impersonating platform users
PlatformFake = "fake" PlatformFake = "fake"

59
meta.go
View File

@@ -68,25 +68,40 @@ var (
) )
var ( var (
meteoraInitializeLbPairDiscriminator = calculateDiscriminator("global:initialize_lb_pair2") meteoraInitializeLbPairDiscriminator = calculateDiscriminator("global:initialize_lb_pair2")
meteoraInitializeLbPairEventDiscriminator = calculateDiscriminator("event:LbPairCreate") meteoraInitializeLbPairEventDiscriminator = calculateDiscriminator("event:LbPairCreate")
meteoraDlmmSwapDiscriminator = calculateDiscriminator("global:swap") meteoraDlmmSwapDiscriminator = calculateDiscriminator("global:swap")
meteoraDlmmSwap2Discriminator = calculateDiscriminator("global:swap2") meteoraDlmmSwap2Discriminator = calculateDiscriminator("global:swap2")
meteoraDlmmSwapExactOutDiscriminator = calculateDiscriminator("global:swap_exact_out") meteoraDlmmSwapExactOutDiscriminator = calculateDiscriminator("global:swap_exact_out")
meteoraDlmmSwapExactOut2Discriminator = calculateDiscriminator("global:swap_exact_out2") meteoraDlmmSwapExactOut2Discriminator = calculateDiscriminator("global:swap_exact_out2")
meteoraDlmmSwapWithPriceImpactDiscriminator = calculateDiscriminator("global:swap_with_price_impact") meteoraDlmmSwapWithPriceImpactDiscriminator = calculateDiscriminator("global:swap_with_price_impact")
meteoraDlmmSwapWithPriceImpact2Discriminator = calculateDiscriminator("global:swap_with_price_impact2") meteoraDlmmSwapWithPriceImpact2Discriminator = calculateDiscriminator("global:swap_with_price_impact2")
meteoraDlmmSwapEventDiscriminator = calculateDiscriminator("event:Swap") meteoraDlmmInitializePositionDiscriminator = calculateDiscriminator("global:initialize_position")
meteoraDlmmAddLiquidityDiscriminator = calculateDiscriminator("global:add_liquidity") meteoraDlmmInitializePosition2Discriminator = calculateDiscriminator("global:initialize_position2")
meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2") meteoraDlmmInitializePositionByOperatorDiscriminator = calculateDiscriminator("global:initialize_position_by_operator")
meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy") meteoraDlmmInitializePositionPdaDiscriminator = calculateDiscriminator("global:initialize_position_pda")
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2") meteoraDlmmClosePositionDiscriminator = calculateDiscriminator("global:close_position")
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity") meteoraDlmmClosePosition2Discriminator = calculateDiscriminator("global:close_position2")
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2") meteoraDlmmClosePositionIfEmptyDiscriminator = calculateDiscriminator("global:close_position_if_empty")
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range") meteoraDlmmSwapEventDiscriminator = calculateDiscriminator("event:Swap")
meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2") meteoraDlmmAddLiquidityDiscriminator = calculateDiscriminator("global:add_liquidity")
meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity") meteoraDlmmAddLiquidity2Discriminator = calculateDiscriminator("global:add_liquidity2")
meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity") meteoraDlmmAddLiquidityByStrategyDiscriminator = calculateDiscriminator("global:add_liquidity_by_strategy")
meteoraDlmmAddLiquidityByStrategy2Discriminator = calculateDiscriminator("global:add_liquidity_by_strategy2")
meteoraDlmmClaimFeeDiscriminator = calculateDiscriminator("global:claim_fee")
meteoraDlmmClaimFee2Discriminator = calculateDiscriminator("global:claim_fee2")
meteoraDlmmRebalanceLiquidityDiscriminator = calculateDiscriminator("global:rebalance_liquidity")
meteoraDlmmRemoveLiquidityDiscriminator = calculateDiscriminator("global:remove_liquidity")
meteoraDlmmRemoveLiquidity2Discriminator = calculateDiscriminator("global:remove_liquidity2")
meteoraDlmmRemoveLiquidityByRangeDiscriminator = calculateDiscriminator("global:remove_liquidity_by_range")
meteoraDlmmRemoveLiquidityByRange2Discriminator = calculateDiscriminator("global:remove_liquidity_by_range2")
meteoraDlmmAddLiquidityEventDiscriminator = calculateDiscriminator("event:AddLiquidity")
meteoraDlmmClaimFeeEventDiscriminator = calculateDiscriminator("event:ClaimFee")
meteoraDlmmClaimFee2EventDiscriminator = calculateDiscriminator("event:ClaimFee2")
meteoraDlmmPositionCloseEventDiscriminator = calculateDiscriminator("event:PositionClose")
meteoraDlmmPositionCreateEventDiscriminator = calculateDiscriminator("event:PositionCreate")
meteoraDlmmRebalancingEventDiscriminator = calculateDiscriminator("event:Rebalancing")
meteoraDlmmRemoveLiquidityEventDiscriminator = calculateDiscriminator("event:RemoveLiquidity")
) )
var ( var (
@@ -200,8 +215,10 @@ var (
const ( const (
raydiumV4InitializePoolDiscriminator = uint8(1) raydiumV4InitializePoolDiscriminator = uint8(1)
raydiumV4SwapBaseInDiscriminator = uint8(9) raydiumV4SwapBaseInDiscriminator = uint8(9)
raydiumV4SwapBaseOutDiscriminator = uint8(11) raydiumV4SwapBaseOutDiscriminator = uint8(11)
raydiumV4SwapBaseInV2Discriminator = uint8(16)
raydiumV4SwapBaseOutV2Discriminator = uint8(17)
raydiumV4AddLiquidityDiscriminator = uint8(3) raydiumV4AddLiquidityDiscriminator = uint8(3)
raydiumV4RemoveLiquidityDiscriminator = uint8(4) raydiumV4RemoveLiquidityDiscriminator = uint8(4)

View File

@@ -55,6 +55,62 @@ type dlmmRemoveLiquidityEvent struct {
ActiveBinId int32 ActiveBinId int32
} }
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
}
type dlmmBinLiquidityDistribution struct { type dlmmBinLiquidityDistribution struct {
BinId int32 BinId int32
DistributionX uint16 DistributionX uint16
@@ -127,6 +183,18 @@ type dlmmRemoveLiquidityByRange2Args struct {
RemainingAccountsInfo dlmmRemainingAccountsInfo RemainingAccountsInfo dlmmRemainingAccountsInfo
} }
type dlmmInitializePositionArgs struct {
LowerBinId int32
Width int32
}
type dlmmInitializePositionByOperatorArgs struct {
LowerBinId int32
Width int32
FeeOwner solana.PublicKey
LockReleasePoint uint64
}
type dlmmRemainingAccountsInfo struct{} type dlmmRemainingAccountsInfo struct{}
func (dlmmRemainingAccountsInfo) UnmarshalWithDecoder(decoder *agbinary.Decoder) error { func (dlmmRemainingAccountsInfo) UnmarshalWithDecoder(decoder *agbinary.Decoder) error {
@@ -200,6 +268,9 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
switch discriminator { switch discriminator {
case meteoraInitializeLbPairDiscriminator: case meteoraInitializeLbPairDiscriminator:
return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset) return metaoradlmmInitializeParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmInitializePositionDiscriminator, meteoraDlmmInitializePosition2Discriminator,
meteoraDlmmInitializePositionByOperatorDiscriminator, meteoraDlmmInitializePositionPdaDiscriminator:
return metaoradlmmPositionCreateParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmSwapDiscriminator, meteoraDlmmSwapExactOutDiscriminator, meteoraDlmmSwapWithPriceImpactDiscriminator: case meteoraDlmmSwapDiscriminator, meteoraDlmmSwapExactOutDiscriminator, meteoraDlmmSwapWithPriceImpactDiscriminator:
return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset) return metaoradlmmSwapParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmSwap2Discriminator, meteoraDlmmSwapExactOut2Discriminator, meteoraDlmmSwapWithPriceImpact2Discriminator: case meteoraDlmmSwap2Discriminator, meteoraDlmmSwapExactOut2Discriminator, meteoraDlmmSwapWithPriceImpact2Discriminator:
@@ -207,9 +278,15 @@ func metaoradlmmParser(tx *Tx, instruction Instruction, innerInstructions InnerI
case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator, case meteoraDlmmAddLiquidityDiscriminator, meteoraDlmmAddLiquidity2Discriminator,
meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator: meteoraDlmmAddLiquidityByStrategyDiscriminator, meteoraDlmmAddLiquidityByStrategy2Discriminator:
return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset) return metaoradlmmAddLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmClaimFeeDiscriminator, meteoraDlmmClaimFee2Discriminator:
return metaoradlmmClaimFeeParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmRebalanceLiquidityDiscriminator:
return metaoradlmmRebalanceLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator, case meteoraDlmmRemoveLiquidityDiscriminator, meteoraDlmmRemoveLiquidity2Discriminator,
meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator: meteoraDlmmRemoveLiquidityByRangeDiscriminator, meteoraDlmmRemoveLiquidityByRange2Discriminator:
return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset) return metaoradlmmRemoveLiquidityParser(tx, instruction, innerInstructions, offset)
case meteoraDlmmClosePositionDiscriminator, meteoraDlmmClosePosition2Discriminator, meteoraDlmmClosePositionIfEmptyDiscriminator:
return metaoradlmmPositionCloseParser(tx, instruction, innerInstructions, offset)
default: default:
return nil, increaseOffset(offset), InstructionIgnoredError return nil, increaseOffset(offset), InstructionIgnoredError
} }
@@ -266,6 +343,141 @@ func metaoradlmmInitializeParser(tx *Tx, instruction Instruction, innerInstructi
return []Swap{swap}, offset, nil 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, 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
}
func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
result := tx.rawTx result := tx.rawTx
@@ -457,6 +669,7 @@ func metaoradlmmSwapParser(tx *Tx, instruction Instruction, innerInstructions In
UserBaseBalance: userBase, UserBaseBalance: userBase,
UserQuoteBalance: userQuote, UserQuoteBalance: userQuote,
EntryContract: entryContract, EntryContract: entryContract,
ActiveBinId: swapEvent.EndBinId,
StartBinId: swapEvent.StartBinId, StartBinId: swapEvent.StartBinId,
EndBinId: swapEvent.EndBinId, EndBinId: swapEvent.EndBinId,
} }
@@ -634,7 +847,7 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
swap := Swap{ swap := Swap{
Program: SolProgramMeteoraDLMM, Program: SolProgramMeteoraDLMM,
Event: "add_liquidity", Event: "add",
Pool: pool, Pool: pool,
BaseMint: baseMint, BaseMint: baseMint,
QuoteMint: quoteMint, QuoteMint: quoteMint,
@@ -650,9 +863,11 @@ func metaoradlmmAddLiquidityParser(tx *Tx, instruction Instruction, innerInstruc
UserBaseBalance: userBase, UserBaseBalance: userBase,
UserQuoteBalance: userQuote, UserQuoteBalance: userQuote,
EntryContract: entryContract, EntryContract: entryContract,
ActiveBinId: addEvent.ActiveBinId,
StartBinId: startBinId, StartBinId: startBinId,
EndBinId: endBinId, EndBinId: endBinId,
BinChanges: binChanges, BinChanges: binChanges,
PositionAccount: result.accountList[accounts.positionIdx],
} }
return []Swap{swap}, offset, nil return []Swap{swap}, offset, nil
@@ -682,6 +897,7 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
binChanges []DlmmBinLiquidityChange binChanges []DlmmBinLiquidityChange
startBinId int32 startBinId int32
endBinId int32 endBinId int32
removeBp int32
) )
switch discriminator { switch discriminator {
@@ -692,6 +908,7 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
} }
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval) binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
case meteoraDlmmRemoveLiquidity2Discriminator: case meteoraDlmmRemoveLiquidity2Discriminator:
var args dlmmRemoveLiquidity2Args var args dlmmRemoveLiquidity2Args
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -699,6 +916,7 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
} }
binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval) binChanges = dlmmBinChangesFromReduction(args.BinLiquidityRemoval)
startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval) startBinId, endBinId = dlmmMinMaxBinIdFromReduction(args.BinLiquidityRemoval)
removeBp = dlmmCommonRemoveBp(args.BinLiquidityRemoval)
case meteoraDlmmRemoveLiquidityByRangeDiscriminator: case meteoraDlmmRemoveLiquidityByRangeDiscriminator:
var args dlmmRemoveLiquidityByRangeArgs var args dlmmRemoveLiquidityByRangeArgs
if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil { if err := agbinary.NewBorshDecoder(decode[8:]).Decode(&args); err != nil {
@@ -706,6 +924,7 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
} }
startBinId = args.FromBinId startBinId = args.FromBinId
endBinId = args.ToBinId endBinId = args.ToBinId
removeBp = int32(args.BpsToRemove)
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove) binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
case meteoraDlmmRemoveLiquidityByRange2Discriminator: case meteoraDlmmRemoveLiquidityByRange2Discriminator:
var args dlmmRemoveLiquidityByRange2Args var args dlmmRemoveLiquidityByRange2Args
@@ -714,6 +933,7 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
} }
startBinId = args.FromBinId startBinId = args.FromBinId
endBinId = args.ToBinId endBinId = args.ToBinId
removeBp = int32(args.BpsToRemove)
binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove) binChanges = dlmmBinChangesFromRange(startBinId, endBinId, args.BpsToRemove)
default: default:
return nil, increaseOffset(offset), InstructionIgnoredError return nil, increaseOffset(offset), InstructionIgnoredError
@@ -802,7 +1022,7 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
swap := Swap{ swap := Swap{
Program: SolProgramMeteoraDLMM, Program: SolProgramMeteoraDLMM,
Event: "remove_liquidity", Event: "remove",
Pool: pool, Pool: pool,
BaseMint: baseMint, BaseMint: baseMint,
QuoteMint: quoteMint, QuoteMint: quoteMint,
@@ -818,14 +1038,291 @@ func metaoradlmmRemoveLiquidityParser(tx *Tx, instruction Instruction, innerInst
UserBaseBalance: userBase, UserBaseBalance: userBase,
UserQuoteBalance: userQuote, UserQuoteBalance: userQuote,
EntryContract: entryContract, EntryContract: entryContract,
ActiveBinId: removeEvent.ActiveBinId,
StartBinId: startBinId, StartBinId: startBinId,
EndBinId: endBinId, EndBinId: endBinId,
RemoveBp: removeBp,
BinChanges: binChanges, BinChanges: binChanges,
PositionAccount: result.accountList[accounts.positionIdx],
} }
return []Swap{swap}, offset, nil 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,
BinChanges: dlmmBinChangesFromRange(event.OldMinBinId, event.OldMaxBinId, 0),
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,
BinChanges: dlmmBinChangesFromRange(event.NewMinBinId, event.NewMaxBinId, 0),
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) { func dlmmSelectBaseQuote(tokenX, tokenY solana.PublicKey) (baseMint solana.PublicKey, quoteMint solana.PublicKey, baseIsX bool) {
priority := []solana.PublicKey{wSolMint, usdcMint, usd1Mint} priority := []solana.PublicKey{wSolMint, usdcMint, usd1Mint}
for _, mint := range priority { for _, mint := range priority {
@@ -887,6 +1384,116 @@ func dlmmRemoveLiquidityEventFromInnerInstructions(innerInstructions InnerInstru
return dlmmRemoveLiquidityEvent{}, increaseOffset(offset), fmt.Errorf("meteora dlmm remove liquidity event not found, offset, %d, %d", offset[0], prefixLen) 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, 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
}
func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) { func dlmmDecodeAddLiquidityEvent(data []byte) (dlmmAddLiquidityEvent, bool) {
switch { switch {
case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]): case len(data) >= 8 && bytes.Equal(data[:8], meteoraDlmmAddLiquidityEventDiscriminator[:]):
@@ -929,6 +1536,140 @@ func dlmmDecodeRemoveLiquidityEvent(data []byte) (dlmmRemoveLiquidityEvent, bool
} }
} }
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) { func resolveDlmmSwapAccounts(result *RawTx, accounts []int) (dlmmSwapAccounts, error) {
if len(accounts) < 13 { if len(accounts) < 13 {
return dlmmSwapAccounts{}, fmt.Errorf("accounts too short, expected at least 13") return dlmmSwapAccounts{}, fmt.Errorf("accounts too short, expected at least 13")
@@ -1046,6 +1787,93 @@ func resolveDlmmLiquidityAccounts(result *RawTx, accounts []int) (dlmmLiquidityA
}, nil }, 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
}
func dlmmTokenDecimals(result *RawTx, accountIndex int) (uint8, bool) { func dlmmTokenDecimals(result *RawTx, accountIndex int) (uint8, bool) {
for _, meta := range result.Meta.PostTokenBalances { for _, meta := range result.Meta.PostTokenBalances {
if meta.AccountIndex == accountIndex { if meta.AccountIndex == accountIndex {
@@ -1182,6 +2010,26 @@ func dlmmBinChangesFromRange(startBinId, endBinId int32, bpsToRemove uint16) []D
return changes return changes
} }
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) { func dlmmMinMaxBinIdFromDistribution(dist []dlmmBinLiquidityDistribution) (int32, int32) {
if len(dist) == 0 { if len(dist) == 0 {
return 0, 0 return 0, 0

View File

@@ -127,6 +127,9 @@ func (tx *Tx) Parser() error {
//fmt.Printf("parser failed tx error: %s, block: %d tx: %s\n", err, tx.Block, tx.GetTxHash()) //fmt.Printf("parser failed tx error: %s, block: %d tx: %s\n", err, tx.Block, tx.GetTxHash())
} }
if len(swaps) > 0 { if len(swaps) > 0 {
for i := range swaps {
swaps[i].InstrIdx = tx.Err.Index
}
tx.Swaps = swaps tx.Swaps = swaps
} }
for i, instr := range tx.rawTx.Transaction.Message.Instructions { for i, instr := range tx.rawTx.Transaction.Message.Instructions {
@@ -172,6 +175,7 @@ func (tx *Tx) Parser() error {
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9)) swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
} }
} }
swap.InstrIdx = uint8(i)
tx.Swaps = append(tx.Swaps, swap) tx.Swaps = append(tx.Swaps, swap)
} }
@@ -219,6 +223,8 @@ func (tx *Tx) Parser() error {
swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9)) swap.AfterSOLBalance = decimal.NewFromUint64(tx.rawTx.Meta.PostBalances[userIdx]).Div(decimal.NewFromInt(1e9))
} }
} }
swap.InstrIdx = uint8(i)
swap.InnerIdx = uint8(j)
tx.Swaps = append(tx.Swaps, swap) tx.Swaps = append(tx.Swaps, swap)
} }
// tx.Swaps = append(tx.Swaps, swaps...) // tx.Swaps = append(tx.Swaps, swaps...)

View File

@@ -29,6 +29,8 @@ func raydiumV4Parser(tx *Tx, instruction Instruction, innerInstructions InnerIns
return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset) return raydiumv4WithdrawPNLParser(tx, instruction, innerInstructions, offset)
case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator: case raydiumV4SwapBaseInDiscriminator, raydiumV4SwapBaseOutDiscriminator:
return raydiumv4SwapParser(tx, instruction, innerInstructions, offset) return raydiumv4SwapParser(tx, instruction, innerInstructions, offset)
case raydiumV4SwapBaseInV2Discriminator, raydiumV4SwapBaseOutV2Discriminator:
return raydiumv4SwapV2Parser(tx, instruction, innerInstructions, offset)
default: default:
return nil, increaseOffset(offset), InstructionIgnoredError return nil, increaseOffset(offset), InstructionIgnoredError
@@ -397,3 +399,99 @@ func raydiumv4SwapParser(tx *Tx, instruction Instruction, innerInstructions Inne
}, },
}, offset, nil }, offset, nil
} }
func raydiumv4SwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
accountsLen := len(instruction.Accounts)
if accountsLen != 8 {
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swapv2 instruction, offset %d, %d", offset[0], offset[1])
}
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
ammAccount := tx.rawTx.accountList[instruction.Accounts[1]]
user := tx.rawTx.accountList[instruction.Accounts[7]]
userSourceTokenAccount := tx.rawTx.accountList[instruction.Accounts[5]]
userDestinationTokenAccount := tx.rawTx.accountList[instruction.Accounts[6]]
baseVaultIdx := instruction.Accounts[3]
quoteVaultIdx := instruction.Accounts[4]
baseTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, baseVaultIdx)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("failed to get base vault balance after tx: %v", err)
}
quoteTokenbalance, err := getTokenBalanceAfterTx(tx.rawTx, quoteVaultIdx)
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("failed to get quote vault balance after tx: %v", err)
}
inners, err := getInnerInstructions(innerInstructions, offset[1])
if err != nil {
return nil, increaseOffset(offset), err
}
var nextIndex int
var srcFound, destFound bool
var baseAmount, quoteAmount decimal.Decimal
var event string
for i, inner := range inners {
from, to, amount, err := parseTokenTransfer(tx.rawTx, inner)
if err != nil {
continue
}
if from.Equals(userSourceTokenAccount) && !srcFound {
if to.Equals(tx.rawTx.accountList[baseVaultIdx]) {
event = "sell"
baseAmount = decimal.NewFromUint64(amount)
srcFound = true
} else if to.Equals(tx.rawTx.accountList[quoteVaultIdx]) {
event = "buy"
quoteAmount = decimal.NewFromUint64(amount)
srcFound = true
}
} else if to.Equals(userDestinationTokenAccount) && !destFound {
if from.Equals(tx.rawTx.accountList[quoteVaultIdx]) {
event = "sell"
quoteAmount = decimal.NewFromUint64(amount)
destFound = true
} else if from.Equals(tx.rawTx.accountList[baseVaultIdx]) {
event = "buy"
baseAmount = decimal.NewFromUint64(amount)
destFound = true
}
}
if srcFound && destFound {
nextIndex = i + 1
break
}
}
if !srcFound || !destFound {
return nil, increaseOffset(offset), fmt.Errorf("raydiumv4 failed to find token transfer inner instruction for swapv2, offset %d, %d", offset[0], offset[1])
}
offset[1] += uint(nextIndex + 1)
userBase := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[5])
userQuote := getAccountBalanceAfterTx(tx.rawTx, instruction.Accounts[6])
baseReserve, _ := decimal.NewFromString(baseTokenbalance.UITokenAmount.Amount)
quoteReserve, _ := decimal.NewFromString(quoteTokenbalance.UITokenAmount.Amount)
return []Swap{
{
Program: SolProgramRaydiumV4,
Event: event,
Pool: ammAccount,
BaseMint: baseTokenbalance.MintAccount,
QuoteMint: quoteTokenbalance.MintAccount,
BaseTokenProgram: baseTokenbalance.ProgramIDAccount,
QuoteTokenProgram: quoteTokenbalance.ProgramIDAccount,
BaseMintDecimals: uint8(baseTokenbalance.UITokenAmount.Decimals),
QuoteMintDecimals: uint8(quoteTokenbalance.UITokenAmount.Decimals),
User: user,
BaseAmount: baseAmount,
QuoteAmount: quoteAmount,
BaseReserve: baseReserve,
QuoteReserve: quoteReserve,
Mayhem: false,
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
EntryContract: entryContract,
},
}, offset, nil
}

12
tx.go
View File

@@ -12,6 +12,9 @@ type Swap struct {
TxIndex int TxIndex int
InstrIdx uint8
InnerIdx uint8
Pool solana.PublicKey Pool solana.PublicKey
BaseMint solana.PublicKey BaseMint solana.PublicKey
QuoteMint solana.PublicKey QuoteMint solana.PublicKey
@@ -45,9 +48,12 @@ type Swap struct {
AfterSOLBalance decimal.Decimal AfterSOLBalance decimal.Decimal
//For meteora dlmm //For meteora dlmm
StartBinId int32 ActiveBinId int32
EndBinId int32 StartBinId int32
BinChanges []DlmmBinLiquidityChange EndBinId int32
RemoveBp int32
BinChanges []DlmmBinLiquidityChange
PositionAccount solana.PublicKey
ConsumeUnit uint64 ConsumeUnit uint64
} }