diff --git a/pkg/shreder/program_maestro.go b/pkg/shreder/program_maestro.go index acfea1c..ecc2fb0 100644 --- a/pkg/shreder/program_maestro.go +++ b/pkg/shreder/program_maestro.go @@ -106,7 +106,6 @@ func maestroMultiSwap2DexName(dexID uint8) string { return "OrcaWhirPool" case 9: return "MeteoraPools" - case 10: return "MeteoraDynamicBondingCurve" default: @@ -125,20 +124,7 @@ func parseMaestroInstruction(tx VersionedTransaction, idx int) (TxSignalBatch, e return parseMaestroInstructionDataAndAccounts(tx, ix.Data, ix.Accounts) } -func parseMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixData []byte, ixAccounts []uint8) (TxSignalBatch, error) { - args, err := decodeMaestroMultiSwap2Args(ixData) - if err != nil { - return nil, nil - } - // only decode pump amm - if len(args.Routes) != 1 { - return nil, nil - } - - if args.Routes[0].DexID != 4 { - return nil, nil - } - +func parsePumpAMMMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixAccounts []uint8, args *MaestroMultiSwap2Args) (TxSignalBatch, error) { var ( event string token0Amount uint64 @@ -255,3 +241,91 @@ func parseMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixData []by }, }, nil } + +func parseRaydiumLaunchLabMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixAccounts []uint8, args *MaestroMultiSwap2Args) (TxSignalBatch, error) { + var ( + event string + token0Amount uint64 + token1Amount uint64 + + // pool solana.PublicKey + baseMint solana.PublicKey + quoteMint solana.PublicKey + + err error + ) + + routeFlag := args.Routes[0].RouteKeyIdx + if routeFlag == 0x0b { + event = "buy" + token0Amount = args.MinAmountOut + token1Amount = args.AmountIn + if len(ixAccounts) < 18 { + return nil, nil + } + quoteMint, err = tx.GetAccount(int(ixAccounts[7])) + if err != nil { + return nil, err + } + baseMint, err = tx.GetAccount(int(ixAccounts[17])) + if err != nil { + return nil, err + } + } else if routeFlag == 0x08 { + event = "sell" + token0Amount = args.AmountIn + token1Amount = args.MinAmountOut + if len(ixAccounts) < 18 { + return nil, nil + } + quoteMint, err = tx.GetAccount(int(ixAccounts[17])) + if err != nil { + return nil, err + } + baseMint, err = tx.GetAccount(int(ixAccounts[7])) + if err != nil { + return nil, err + } + } else { + return nil, nil + } + + if !quoteMint.Equals(wrappedSOL) { + return nil, nil + } + + return TxSignalBatch{ + &TxSignal{ + TxHash: tx.Signatures[0].String(), + Maker: tx.StaticAccountKeys[0].String(), + Token0Address: baseMint.String(), + Token1Address: wsolMint, + Token0Amount: formatTokenAmount(token0Amount), + Token1Amount: formatTokenAmount(token1Amount), + Event: event, + Program: "RaydiumLaunchLab", + ExactSOL: event == "buy", + Token0AmountUint64: token0Amount, + Token1AmountUint64: token1Amount, + }, + }, nil +} + +func parseMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixData []byte, ixAccounts []uint8) (TxSignalBatch, error) { + args, err := decodeMaestroMultiSwap2Args(ixData) + if err != nil { + return nil, nil + } + // only decode 1 route + if len(args.Routes) != 1 { + return nil, nil + } + + if args.Routes[0].DexID == 4 { + return parsePumpAMMMaestroInstructionDataAndAccounts(tx, ixAccounts, args) + } else if args.Routes[0].DexID == 3 { + return parseRaydiumLaunchLabMaestroInstructionDataAndAccounts(tx, ixAccounts, args) + } + + return nil, nil +} diff --git a/pkg/shreder/txparser_test.go b/pkg/shreder/txparser_test.go index a45373d..b7bdac5 100644 --- a/pkg/shreder/txparser_test.go +++ b/pkg/shreder/txparser_test.go @@ -1012,3 +1012,115 @@ func TestParseFlasBonkSell(t *testing.T) { t.Fatalf("expected ExactSOL false, got true") } } + +func TestParseMaestroBonkBuy(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("HPjn8EFrUcMxqQrdRzJkgXp85cGUHLFVkY4nZ7EUBbir")) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "LuGGhtCU5enHY2J8qt5KAZybvQyokxKn4NxkhQfKz6RbkxW1anU9vHfAXeEVsjM49mtPmeyyVKKW1myaAXt6BhJ"), + 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 != "maestro" { + t.Fatalf("expected maestro signal, got %s", signal.Label) + } + if signal.Event != "buy" { + t.Fatalf("expected buy event, got %s", signal.Event) + } + if signal.Maker != "Gyz2QP89RuoFG55V2xVpyGiG7rMya41j1ZbFNbd1WfF9" { + t.Fatalf("expected maker Gyz2QP89RuoFG55V2xVpyGiG7rMya41j1ZbFNbd1WfF9, got %s", signal.Maker) + } + if signal.Token0Address != "2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk" { + t.Fatalf("expected token0 address 2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 11089351947578 { + t.Fatalf("expected token0 amount 1052495896871, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 1000000000 { + t.Fatalf("expected token1 amount 1000000000, got %d", signal.Token1AmountUint64) + } + if !signal.ExactSOL { + t.Fatalf("expected ExactSOL true, got false") + } +} + +func TestParseMaestroBonkSell(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("HPjn8EFrUcMxqQrdRzJkgXp85cGUHLFVkY4nZ7EUBbir")) + ch := make(chan TxSignal) + closed := make(chan struct{}) + go func() { + ParseTransactionForSubscribe( + context.Background(), + getTransaction(t, client, "3cXpA8C5uizp1iK8fV8eoxoonT5BcT7G52wN9aRsRwi9pUCUyuDt2FwXVVtbkvocxoAD82ZQWjCaLRgswgcszTHQ"), + 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 != "maestro" { + t.Fatalf("expected maestro signal, got %s", signal.Label) + } + if signal.Event != "sell" { + t.Fatalf("expected sell event, got %s", signal.Event) + } + if signal.Maker != "fwBVjnPQHwrAVWP4VnwbA1Y9BiDWnixJ39G5PA59sZv" { + t.Fatalf("expected maker fwBVjnPQHwrAVWP4VnwbA1Y9BiDWnixJ39G5PA59sZv, got %s", signal.Maker) + } + if signal.Token0Address != "9553NoaZEQGYsttrym5w85RQVHhwVi3BfNU9GLfEbonk" { + t.Fatalf("expected token0 address 9553NoaZEQGYsttrym5w85RQVHhwVi3BfNU9GLfEbonk, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 11299453877090 { + t.Fatalf("expected token0 amount 11299453877090, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 892516080 { + t.Fatalf("expected token1 amount 892516080, got %d", signal.Token1AmountUint64) + } + if signal.ExactSOL { + t.Fatalf("expected ExactSOL false, got true") + } +}