diff --git a/pkg/shreder/program_bloomrouter.go b/pkg/shreder/program_bloomrouter.go index 5250835..1b3eadf 100644 --- a/pkg/shreder/program_bloomrouter.go +++ b/pkg/shreder/program_bloomrouter.go @@ -26,11 +26,80 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int) return nil, nil } + findPumpFun := func() (solana.PublicKey, solana.PublicKey, error) { + var mint solana.PublicKey + foundPumpFun := false + for i, acctIdx := range instruction.Accounts { + key, err := tx.GetAccount(int(acctIdx)) + if err != nil { + return solana.PublicKey{}, solana.PublicKey{}, err + } + if key.Equals(pumpFunAccount) { + if i+2 >= len(instruction.Accounts) { + return solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short for pumpfun mint, idx=%d len=%d", i, len(instruction.Accounts)) + } + mintKey, err := tx.GetAccount(int(instruction.Accounts[i+2])) + if err != nil { + return solana.PublicKey{}, solana.PublicKey{}, err + } + mint = mintKey + foundPumpFun = true + break + } + } + if !foundPumpFun { + return solana.PublicKey{}, solana.PublicKey{}, nil + } + return mint, wrappedSOL, nil + } + + findRaydiumLaunchLab := func(isBuy bool) (solana.PublicKey, solana.PublicKey, error) { + offset := 0 + if isBuy { + offset = 10 + } else { + offset = 9 + } + var base solana.PublicKey + var quote solana.PublicKey + foundRaydiumLaunchLab := false + for i, acctIdx := range instruction.Accounts { + key, err := tx.GetAccount(int(acctIdx)) + if err != nil { + return solana.PublicKey{}, solana.PublicKey{}, err + } + if key.Equals(raydiumLaunchLabProgramID) { + if i+offset+1 >= len(instruction.Accounts) { + return solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short for raydium launch lab mint, idx=%d len=%d", i, len(instruction.Accounts)) + } + var err error + base, err = tx.GetAccount(int(instruction.Accounts[i+offset])) + if err != nil { + return solana.PublicKey{}, solana.PublicKey{}, err + } + quote, err = tx.GetAccount(int(instruction.Accounts[i+offset+1])) + if err != nil { + return solana.PublicKey{}, solana.PublicKey{}, err + } + foundRaydiumLaunchLab = true + break + } + } + if !foundRaydiumLaunchLab { + return solana.PublicKey{}, solana.PublicKey{}, nil + } + return base, quote, nil + } + var ( amount uint64 sol uint64 exactIn bool event string + program string + base solana.PublicKey + quote solana.PublicKey + err error ) args, err := decodeBloomRouterArgs(instruction.Data) @@ -41,8 +110,39 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int) case 0: event = "buy" exactIn = true + program = "Pump" + base, quote, err = findPumpFun() + if err != nil { + return nil, err + } case 1: event = "sell" + program = "Pump" + base, quote, err = findPumpFun() + if err != nil { + return nil, err + } + case 0x0b00: + event = "buy" + exactIn = true + program = "RaydiumLaunchLab" + base, quote, err = findRaydiumLaunchLab(true) + if err != nil { + return nil, err + } + if !quote.Equals(wrappedSOL) { + return nil, nil + } + case 0x0b01: + event = "sell" + program = "RaydiumLaunchLab" + base, quote, err = findRaydiumLaunchLab(false) + if err != nil { + return nil, err + } + if !quote.Equals(wrappedSOL) { + return nil, nil + } default: return nil, nil } @@ -61,43 +161,17 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int) return nil, err } - var mint solana.PublicKey - foundPumpFun := false - for i, acctIdx := range instruction.Accounts { - key, err := tx.GetAccount(int(acctIdx)) - if err != nil { - return nil, err - } - if key.Equals(pumpFunAccount) { - if i+2 >= len(instruction.Accounts) { - return nil, fmt.Errorf("accounts too short for pumpfun mint, idx=%d len=%d", i, len(instruction.Accounts)) - } - mintKey, err := tx.GetAccount(int(instruction.Accounts[i+2])) - if err != nil { - return nil, err - } - mint = mintKey - foundPumpFun = true - break - } - } - if !foundPumpFun { - return nil, nil - } - return TxSignalBatch{&TxSignal{ TxHash: tx.Signatures[0].String(), Label: "bloomrouter", Maker: maker.String(), - Token0Address: mint.String(), - Token1Address: wsolMint, + Token0Address: base.String(), + Token1Address: quote.String(), Token0Amount: formatTokenAmount(amount), Token1Amount: formatSolAmount(sol), - Program: "Pump", + Program: program, Event: event, ExactSOL: exactIn, - IsToken2022: false, - IsMayhemMode: false, Block: tx.Block, Token0AmountUint64: amount, Token1AmountUint64: sol, diff --git a/pkg/shreder/txparser_test.go b/pkg/shreder/txparser_test.go index b7bdac5..c4b00d5 100644 --- a/pkg/shreder/txparser_test.go +++ b/pkg/shreder/txparser_test.go @@ -1124,3 +1124,115 @@ func TestParseMaestroBonkSell(t *testing.T) { t.Fatalf("expected ExactSOL false, got true") } } + +func TestParseBloomRouterBonkBuy(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("3xRUDpys1Yy96y5QVBfhW94ukphuDXHsKh4mTAFofA6S")) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "5XEGgHXokNKQpgUUf1zS8LXFRHR7XNBaPiRZxGumFkBH23b3TsTjs6wJ1NRHxf6xRvYBLwXWWJdw7AiNzAAgUzgg"), + 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 != "bloomrouter" { + t.Fatalf("expected bloomrouter signal, got %s", signal.Label) + } + if signal.Event != "buy" { + t.Fatalf("expected buy event, got %s", signal.Event) + } + if signal.Maker != "9ozVSeSsgASFDUjveLNQV697kAavStvpXmKP65oMt2Ji" { + t.Fatalf("expected maker 9ozVSeSsgASFDUjveLNQV697kAavStvpXmKP65oMt2Ji, got %s", signal.Maker) + } + if signal.Token0Address != "6qzh89yisR498GsQysqzj69AW9BJc39LWhXtzxudbonk" { + t.Fatalf("expected token0 address 6qzh89yisR498GsQysqzj69AW9BJc39LWhXtzxudbonk, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 0 { + t.Fatalf("expected token0 amount 0, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 1500000000 { + t.Fatalf("expected token1 amount 1500000000, got %d", signal.Token1AmountUint64) + } + if !signal.ExactSOL { + t.Fatalf("expected ExactSOL true, got false") + } +} + +func TestParseBloomRouterBonkSell(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("3xRUDpys1Yy96y5QVBfhW94ukphuDXHsKh4mTAFofA6S")) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "MMdN29CYKDKDurbhUHn51AyyJ5ZEZq1F1TFJTrVYhRT1moaURqXAHJb2pFus4KrXAdAFo5Hr1Jw4bgE2EWeLXf6"), + 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 != "bloomrouter" { + t.Fatalf("expected bloomrouter signal, got %s", signal.Label) + } + if signal.Event != "sell" { + t.Fatalf("expected sell event, got %s", signal.Event) + } + if signal.Maker != "HWiPtESw8pvhQWm1a7vbHg9rsnee13Pmb7dUGVC3f4mZ" { + t.Fatalf("expected maker HWiPtESw8pvhQWm1a7vbHg9rsnee13Pmb7dUGVC3f4mZ, got %s", signal.Maker) + } + if signal.Token0Address != "6Yd5AGP4E4b1prHxJmuHexUToohiNKiQSG2nXvXWbonk" { + t.Fatalf("expected token0 address 6Yd5AGP4E4b1prHxJmuHexUToohiNKiQSG2nXvXWbonk, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 6448480270053 { + t.Fatalf("expected token0 amount 6448480270053, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 1000 { + t.Fatalf("expected token1 amount 1000, got %d", signal.Token1AmountUint64) + } + if signal.ExactSOL { + t.Fatalf("expected ExactSOL false, got true") + } +}