From 7b2a9af97840d0984c430a9b97c271fe58bf3ee1 Mon Sep 17 00:00:00 2001 From: thloyi Date: Tue, 28 Apr 2026 18:26:43 +0800 Subject: [PATCH] fix flas --- pkg/shreder/program_flas.go | 108 +++++++++++++++++++++++------------ pkg/shreder/txparser_test.go | 40 ++++++++++++- 2 files changed, 107 insertions(+), 41 deletions(-) diff --git a/pkg/shreder/program_flas.go b/pkg/shreder/program_flas.go index 3aa0a49..4c3b1bb 100644 --- a/pkg/shreder/program_flas.go +++ b/pkg/shreder/program_flas.go @@ -1,21 +1,36 @@ package shreder import ( + "encoding/binary" "fmt" "github.com/gagliardetto/solana-go" - "github.com/near/borsh-go" "github.com/shopspring/decimal" ) var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") var ( - flasBuyTokensIX = []byte{0x00, 0x1, 0x1b} - flasSellTokensIX = []byte{0x01, 0x1, 0x1a} - flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2} - flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2} - flasBonkBuyTokensIX = []byte{0x00, 0x2, 0x7} - flasBonkSellTokensIX = []byte{0x01, 0x2, 0x7} + flasBuyTokensIXs = [][]byte{ + {0x00, 0x01, 0x21}, + {0x00, 0x01, 0x1b}, + } + flasSellTokensIXs = [][]byte{ + {0x01, 0x01, 0x1a}, + } + flasAmmBuyTokensIXs = [][]byte{ + {0x00, 0x02, 0x1f}, + {0x00, 0x02, 0x02}, + } + flasAmmSellTokensIXs = [][]byte{ + {0x01, 0x02, 0x1f}, + {0x01, 0x02, 0x02}, + } + flasBonkBuyTokensIXs = [][]byte{ + {0x00, 0x02, 0x07}, + } + flasBonkSellTokensIXs = [][]byte{ + {0x01, 0x02, 0x07}, + } ) type flasArgs struct { @@ -24,6 +39,26 @@ type flasArgs struct { Placeholder [3]uint8 } +func decodeFlasArgs(data []byte) (flasArgs, error) { + if len(data) < 20 { + return flasArgs{}, fmt.Errorf("data too short for args flas instruction, len: %d", len(data)) + } + return flasArgs{ + Amount1: binary.LittleEndian.Uint64(data[1:9]), + Amount2: binary.LittleEndian.Uint64(data[9:17]), + Placeholder: [3]uint8{data[17], data[18], data[19]}, + }, nil +} + +func matchFlasMethod(data []byte, methods [][]byte) bool { + for _, method := range methods { + if matchMethod(data, method) { + return true + } + } + return false +} + func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) { if instructionIndex >= len(tx.Instructions) { return nil, fmt.Errorf("instruction index out of bounds") @@ -40,24 +75,21 @@ func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSign return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data)) } methodData := instruction.Data[17:20] - //if !matchMethod(methodData, flasBuyTokensIX) { - // return nil, nil - //} var ( err error txSignal *TxSignal ) - if matchMethod(methodData, flasBuyTokensIX) { + if matchFlasMethod(methodData, flasBuyTokensIXs) { txSignal, err = parseFlasBuy(tx, instructionIndex) - } else if matchMethod(methodData, flasSellTokensIX) { + } else if matchFlasMethod(methodData, flasSellTokensIXs) { txSignal, err = parseFlasSell(tx, instructionIndex) - } else if matchMethod(methodData, flasAmmBuyTokensIX) { + } else if matchFlasMethod(methodData, flasAmmBuyTokensIXs) { txSignal, err = parseFlasAmmBuy(tx, instructionIndex) - } else if matchMethod(methodData, flasAmmSellTokensIX) { + } else if matchFlasMethod(methodData, flasAmmSellTokensIXs) { txSignal, err = parseFlasAmmSell(tx, instructionIndex) - } else if matchMethod(methodData, flasBonkBuyTokensIX) { + } else if matchFlasMethod(methodData, flasBonkBuyTokensIXs) { txSignal, err = parseFlasBonkBuy(tx, instructionIndex) - } else if matchMethod(methodData, flasBonkSellTokensIX) { + } else if matchFlasMethod(methodData, flasBonkSellTokensIXs) { txSignal, err = parseFlasBonkSell(tx, instructionIndex) } if txSignal != nil { @@ -81,10 +113,16 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, return nil, err } - var args flasArgs - if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + args, err := decodeFlasArgs(instruction.Data) + if err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } + token0Amount := formatTokenAmount(args.Amount1) + token0AmountUint64 := args.Amount1 + if len(instruction.Accounts) == 52 { + token0Amount = decimal.Zero + token0AmountUint64 = 0 + } return &TxSignal{ TxHash: tx.Signatures[0].String(), @@ -92,7 +130,7 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, - Token0Amount: formatTokenAmount(args.Amount1), + Token0Amount: token0Amount, Token1Amount: formatSolAmount(args.Amount2), Program: "PumpAMM", Event: "sell", @@ -100,7 +138,7 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, IsMayhemMode: false, ExactSOL: false, Block: tx.Block, - Token0AmountUint64: args.Amount1, + Token0AmountUint64: token0AmountUint64, Token1AmountUint64: args.Amount2, }, nil } @@ -120,8 +158,8 @@ func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, return nil, err } - var args flasArgs - if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + args, err := decodeFlasArgs(instruction.Data) + if err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } @@ -159,8 +197,8 @@ func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, er return nil, err } - var args flasArgs - if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + args, err := decodeFlasArgs(instruction.Data) + if err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } @@ -196,11 +234,8 @@ func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, err if err != nil { return nil, err } - if len(instruction.Data) > 20 { - instruction.Data = instruction.Data[:20] - } - var args flasArgs - if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + args, err := decodeFlasArgs(instruction.Data) + if err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } @@ -225,7 +260,7 @@ func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, err func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Instructions[instructionIndex] - if len(instruction.Accounts) < 16 { + if len(instruction.Accounts) < 17 { return nil, fmt.Errorf("accounts too short") } @@ -247,11 +282,8 @@ func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, return nil, nil } - if len(instruction.Data) > 20 { - instruction.Data = instruction.Data[:20] - } - var args flasArgs - if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + args, err := decodeFlasArgs(instruction.Data) + if err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } @@ -274,7 +306,7 @@ func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, func parseFlasBonkSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { instruction := tx.Instructions[instructionIndex] - if len(instruction.Accounts) < 16 { + if len(instruction.Accounts) < 17 { return nil, fmt.Errorf("accounts too short") } @@ -296,8 +328,8 @@ func parseFlasBonkSell(tx VersionedTransaction, instructionIndex int) (*TxSignal return nil, nil } - var args flasArgs - if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + args, err := decodeFlasArgs(instruction.Data) + if err != nil { return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) } diff --git a/pkg/shreder/txparser_test.go b/pkg/shreder/txparser_test.go index 2472637..becaa65 100644 --- a/pkg/shreder/txparser_test.go +++ b/pkg/shreder/txparser_test.go @@ -8,7 +8,6 @@ import ( "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" - "github.com/near/borsh-go" ) func TestDecodeAxiomArgs(t *testing.T) { @@ -49,8 +48,8 @@ func TestDecodeAxiomArgs(t *testing.T) { t.Fatalf("failed to decode hex string: %v", err) return } - var args flasArgs - if err := borsh.Deserialize(&args, data[1:]); err != nil { + args, err := decodeFlasArgs(data) + if err != nil { t.Fatalf("failed to decode Axiom args: %v", err) return } @@ -59,6 +58,41 @@ func TestDecodeAxiomArgs(t *testing.T) { } } +func TestDecodeAxiomUpdatedRouteMarkers(t *testing.T) { + tests := []struct { + name string + marker []byte + match func([]byte) bool + }{ + {name: "pump buy 0021", marker: []byte{0x00, 0x01, 0x21}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBuyTokensIXs) }}, + {name: "pump buy 001b", marker: []byte{0x00, 0x01, 0x1b}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBuyTokensIXs) }}, + {name: "pump sell", marker: []byte{0x01, 0x01, 0x1a}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasSellTokensIXs) }}, + {name: "pump amm buy", marker: []byte{0x00, 0x02, 0x1f}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasAmmBuyTokensIXs) }}, + {name: "pump amm sell", marker: []byte{0x01, 0x02, 0x1f}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasAmmSellTokensIXs) }}, + {name: "legacy bonk buy", marker: []byte{0x00, 0x02, 0x07}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBonkBuyTokensIXs) }}, + {name: "legacy bonk sell", marker: []byte{0x01, 0x02, 0x07}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBonkSellTokensIXs) }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]byte, 20) + data[0] = 0 + copy(data[17:20], tt.marker) + args, err := decodeFlasArgs(data) + if err != nil { + t.Fatalf("failed to decode args: %v", err) + } + expected := [3]uint8{tt.marker[0], tt.marker[1], tt.marker[2]} + if args.Placeholder != expected { + t.Fatalf("expected marker %x, got %x", tt.marker, args.Placeholder) + } + if !tt.match(tt.marker) { + t.Fatalf("marker %x did not match route", tt.marker) + } + }) + } +} + func toUpdata(slot uint64, tx *solana.Transaction) *SubscribeUpdateTransaction { signatures := make([][]byte, len(tx.Signatures)) for i, sig := range tx.Signatures {