From a1f9f4f9a9923f7ddaef7d9ee69d005f19822ad0 Mon Sep 17 00:00:00 2001 From: samlior Date: Mon, 23 Mar 2026 16:01:41 +0800 Subject: [PATCH] chore: suppot flas bonk buy/sell --- pkg/shreder/program_flas.go | 89 ++++++++++++++++++++++++++-- pkg/shreder/txparser_test.go | 111 +++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 4 deletions(-) diff --git a/pkg/shreder/program_flas.go b/pkg/shreder/program_flas.go index 9f5a44f..58baf26 100644 --- a/pkg/shreder/program_flas.go +++ b/pkg/shreder/program_flas.go @@ -10,10 +10,12 @@ import ( 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} + 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} ) type flasArgs struct { @@ -53,6 +55,10 @@ func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSign txSignal, err = parseFlasAmmBuy(tx, instructionIndex) } else if matchMethod(methodData, flasAmmSellTokensIX) { txSignal, err = parseFlasAmmSell(tx, instructionIndex) + } else if matchMethod(methodData, flasBonkBuyTokensIX) { + txSignal, err = parseFlasBonkBuy(tx, instructionIndex) + } else if matchMethod(methodData, flasBonkSellTokensIX) { + txSignal, err = parseFlasBonkSell(tx, instructionIndex) } if txSignal != nil { return TxSignalBatch{txSignal}, err @@ -216,3 +222,78 @@ func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, err Token1AmountUint64: args.Amount1, }, nil } + +func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { + instruction := tx.Instructions[instructionIndex] + if len(instruction.Accounts) < 16 { + return nil, fmt.Errorf("accounts too short") + } + + mint, err := tx.GetAccount(int(instruction.Accounts[15])) + if err != nil { + return nil, err + } + user, err := tx.GetAccount(int(instruction.Accounts[1])) + 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 { + return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) + } + + return &TxSignal{ + TxHash: tx.Signatures[0].String(), + Label: "flas", + Maker: user.String(), + Token0Address: mint.String(), + Token1Address: wsolMint, + Token0Amount: formatTokenAmount(args.Amount2), + Token1Amount: formatSolAmount(args.Amount1), + Program: "RaydiumLaunchLab", + Event: "buy", + ExactSOL: true, + Block: tx.Block, + Token0AmountUint64: args.Amount2, + Token1AmountUint64: args.Amount1, + }, nil +} + +func parseFlasBonkSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { + instruction := tx.Instructions[instructionIndex] + if len(instruction.Accounts) < 16 { + return nil, fmt.Errorf("accounts too short") + } + + mint, err := tx.GetAccount(int(instruction.Accounts[15])) + if err != nil { + return nil, err + } + user, err := tx.GetAccount(int(instruction.Accounts[1])) + if err != nil { + return nil, err + } + + var args flasArgs + if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + return nil, fmt.Errorf("failed to parse buy tokens args: %w", err) + } + + return &TxSignal{ + TxHash: tx.Signatures[0].String(), + Label: "flas", + Maker: user.String(), + Token0Address: mint.String(), + Token1Address: wsolMint, + Token0Amount: formatTokenAmount(args.Amount1), + Token1Amount: formatSolAmount(args.Amount2), + Program: "RaydiumLaunchLab", + Event: "sell", + Block: tx.Block, + Token0AmountUint64: args.Amount1, + Token1AmountUint64: args.Amount2, + }, nil +} diff --git a/pkg/shreder/txparser_test.go b/pkg/shreder/txparser_test.go index 25bd8ba..7eebfd4 100644 --- a/pkg/shreder/txparser_test.go +++ b/pkg/shreder/txparser_test.go @@ -896,3 +896,114 @@ func TestParsePumpCreate2(t *testing.T) { t.Fatalf("expected IsCashbackEnabled true, got false") } } + +func TestParseFlasBonkBuy(t *testing.T) { + rpcUrl := os.Getenv("SOL_RPC_URL") + if rpcUrl == "" { + t.Fatalf("SOL_RPC_URL is not set") + } + + client := rpc.New(rpcUrl) + loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("7RKtfATWCe98ChuwecNq8XCzAzfoK3DtZTprFsPMGtio")) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "5ED51fnabzxsPqjswp7R9qbfuTep7avtsQnsYg4R6w2jc9Ys2mMCXFNNnDNvUUhaREJS5Tz1dSfBL1dufXzDsiaX"), + loader, + ch, + closed, + ) + }() + go func() { + <-closed + close(ch) + }() + signals := make([]TxSignal, 0) + for signal := range ch { + signals = append(signals, signal) + } + + if len(signals) != 1 { + t.Fatalf("expected 1 signal, got %d", len(signals)) + } + + signal := signals[0] + if signal.Label != "flas" { + t.Fatalf("expected flas signal, got %s", signal.Label) + } + if signal.Event != "buy" { + t.Fatalf("expected buy event, got %s", signal.Event) + } + if signal.Maker != "75KjigN4rgweGMRu5oWY4DBPELpQ1TYsBQAXuzs7hKVA" { + t.Fatalf("expected maker 75KjigN4rgweGMRu5oWY4DBPELpQ1TYsBQAXuzs7hKVA, got %s", signal.Maker) + } + if signal.Token0Address != "2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk" { + t.Fatalf("expected token0 address 2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 1052495896871 { + t.Fatalf("expected token0 amount 1052495896871, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 99000000 { + t.Fatalf("expected token1 amount 99000000, got %d", signal.Token1AmountUint64) + } + if !signal.ExactSOL { + t.Fatalf("expected ExactSOL true, got false") + } +} + +func TestParseFlasBonkSell(t *testing.T) { + rpcUrl := os.Getenv("SOL_RPC_URL") + if rpcUrl == "" { + t.Fatalf("SOL_RPC_URL is not set") + } + + client := rpc.New(rpcUrl) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "2v3qLsnrJ5KqUDqtzXyc3S9vT6cLvXbaVR6vwfhp4ufC4Sg1vmR5xMdxzrtvErq8kiC8g7d5wLAbEMe8NwXJE5MS"), + nil, + ch, + closed, + ) + }() + go func() { + <-closed + close(ch) + }() + signals := make([]TxSignal, 0) + for signal := range ch { + signals = append(signals, signal) + } + + if len(signals) != 1 { + t.Fatalf("expected 1 signal, got %d", len(signals)) + } + + signal := signals[0] + if signal.Label != "flas" { + t.Fatalf("expected flas signal, got %s", signal.Label) + } + if signal.Event != "sell" { + t.Fatalf("expected sell event, got %s", signal.Event) + } + if signal.Maker != "7rtyqW2yr76Y9iCTvbAzkDdaJU8mbx3ZuzW9sTZ3pV2q" { + t.Fatalf("expected maker 7rtyqW2yr76Y9iCTvbAzkDdaJU8mbx3ZuzW9sTZ3pV2q, got %s", signal.Maker) + } + if signal.Token0Address != "2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk" { + t.Fatalf("expected token0 address 2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 6413676607028 { + t.Fatalf("expected token0 amount 6413676607028, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 249361301 { + t.Fatalf("expected token1 amount 249361301, got %d", signal.Token1AmountUint64) + } + if signal.ExactSOL { + t.Fatalf("expected ExactSOL false, got true") + } +}