From 45107aa8c3b4a542df3c06541303fbc38941c690 Mon Sep 17 00:00:00 2001 From: bijianing97 <826015751@qq.com> Date: Thu, 22 Jan 2026 17:10:13 +0800 Subject: [PATCH] Add JupiterAggregatorV6 pumpfun parse --- cmd/{dlmmparse => txparse}/main.go | 36 +++--- pkg/shreder/juptierv6.go | 175 +++++++++++++++++++++++++++++ pkg/shreder/txparser_test.go | 80 +++++++++++++ 3 files changed, 278 insertions(+), 13 deletions(-) rename cmd/{dlmmparse => txparse}/main.go (82%) diff --git a/cmd/dlmmparse/main.go b/cmd/txparse/main.go similarity index 82% rename from cmd/dlmmparse/main.go rename to cmd/txparse/main.go index 6294c86..f45e1bc 100644 --- a/cmd/dlmmparse/main.go +++ b/cmd/txparse/main.go @@ -13,22 +13,25 @@ import ( "github.com/samlior/libsam/pkg/shreder" ) -// OKX tx -// const dlmmSignature = "4W8gD2iEYyvzpPiW9BhdH5hUrfXhqH46ziLzPkaaxmaA8XXK53erUvrPdZ5cY2XrgWwix1hmRajUnGAiNp4cSGpN" - -const dlmmSignature = "3Kcm9rqG9mJ6PCM5DuUoZ6jk3kzbH7J5GP488E5MKMwodf2NXgddygEcWBmzRBrV2YZFmXtG22gvcJixqdsCjRPn" - +const ( + rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d" + txSignature = "4rkbkLCUQxc89Aq1BZxU1w4LDQtnCtoUJ6VNHmVec8Kqngr5T89xAXahubLFg8DF6iFGzJ39N8V8n6LFtARDUJT9" + labelFilter = "" + enableStats = true +) func main() { - const rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d" - if rpcURL == "" { - log.Fatal("SOL_RPC_URL is not set") + if rpcURL == "" || rpcURL == "REPLACE_WITH_RPC_URL" { + log.Fatal("rpcURL is not set in cmd/dlmmparse/main.go") + } + if txSignature == "" || txSignature == "REPLACE_WITH_TX_SIGNATURE" { + log.Fatal("txSignature is not set in cmd/dlmmparse/main.go") } client := rpc.New(rpcURL) - sig, err := solana.SignatureFromBase58(dlmmSignature) + sig, err := solana.SignatureFromBase58(txSignature) if err != nil { - log.Fatalf("invalid dlmmSignature: %v", err) + log.Fatalf("invalid txSignature: %v", err) } version := uint64(0) tx, err := client.GetTransaction( @@ -72,7 +75,7 @@ func main() { } update := toSubscribeUpdate(tx.Slot, rawTx) - signals := shreder.ParseTransaction(update, nil, true) + signals := shreder.ParseTransaction(update, nil, enableStats) if len(signals) == 0 { fmt.Println("no signals parsed") return @@ -80,7 +83,10 @@ func main() { printed := false for _, signal := range signals { - if signal == nil || signal.Label != "dlmm" { + if signal == nil { + continue + } + if labelFilter != "" && signal.Label != labelFilter { continue } printed = true @@ -95,7 +101,11 @@ func main() { return } - fmt.Println("no dlmm signal parsed, dump all signals:") + if labelFilter != "" { + fmt.Printf("no %s signal parsed, dump all signals:\n", labelFilter) + } else { + fmt.Println("no matching signal parsed, dump all signals:") + } for _, signal := range signals { if signal == nil { continue diff --git a/pkg/shreder/juptierv6.go b/pkg/shreder/juptierv6.go index 8f02204..ec0d41d 100644 --- a/pkg/shreder/juptierv6.go +++ b/pkg/shreder/juptierv6.go @@ -858,6 +858,101 @@ func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) { return ret, i } +type pumpWrappedMatch struct { + IsBuy bool + InAmount uint64 + OutAmount uint64 +} + +func isPumpWrappedBuy(kind SwapKind) bool { + switch kind { + case PumpWrappedBuy, PumpWrappedBuyV2, PumpWrappedBuyV3, PumpWrappedBuyV4: + return true + default: + return false + } +} + +func isPumpWrappedSell(kind SwapKind) bool { + switch kind { + case PumpWrappedSell, PumpWrappedSellV2, PumpWrappedSellV3, PumpWrappedSellV4: + return true + default: + return false + } +} + +func pumpWrappedAtIdx0(in uint64, out uint64, plan []RoutePlanStep) (pumpWrappedMatch, int) { + var ( + ret pumpWrappedMatch + count int + ) + for _, step := range plan { + if step.InputIdx != 0 { + continue + } + if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) { + continue + } + count++ + if count > 1 { + return pumpWrappedMatch{}, count + } + ret.IsBuy = isPumpWrappedBuy(step.Swap.Kind) + ret.InAmount = in * uint64(step.Percent) / 100 + if step.Percent == 100 { + ret.OutAmount = out + } + } + return ret, count +} + +func pumpWrappedAtIdx0V2(in uint64, out uint64, plan []RoutePlanStepV2) (pumpWrappedMatch, int) { + var ( + ret pumpWrappedMatch + count int + ) + for _, step := range plan { + if step.InputIdx != 0 { + continue + } + if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) { + continue + } + count++ + if count > 1 { + return pumpWrappedMatch{}, count + } + ret.IsBuy = isPumpWrappedBuy(step.Swap.Kind) + ret.InAmount = in * uint64(step.Bps) / 10000 + if step.Bps == 10000 { + ret.OutAmount = out + } + } + return ret, count +} + +func findPumpFunMint(staticKeys []solana.PublicKey, accounts []uint8) (solana.PublicKey, bool, error) { + for i, acctIdx := range accounts { + key, err := getStaticKey(staticKeys, int(acctIdx)) + if err != nil { + return solana.PublicKey{}, false, err + } + if !key.Equals(pumpProgramID) { + continue + } + if i+3 >= len(accounts) { + return solana.PublicKey{}, false, nil + } + mint, err := getStaticKey(staticKeys, int(accounts[i+3])) + if err != nil { + return solana.PublicKey{}, false, err + } + return mint, true, nil + } + return solana.PublicKey{}, false, nil +} + // only decodes inputIdx = 0 container pumpSwap instructions for now func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { msg := tx.Message @@ -879,6 +974,9 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( sourceMint solana.PublicKey inputAmount uint64 planCount int + wrapped pumpWrappedMatch + wrappedCnt int + exactOut bool err error ) @@ -891,12 +989,28 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( return nil, err } inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan) + wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.In, args.Out, args.Plan) case bytes.Equal(disc, jupiterSharedAccountsRouteV2): args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:]) if err != nil { return nil, err } inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan) + wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.In, args.QuotedOut, args.RoutePlan) + case bytes.Equal(disc, jupiterExactOutRouteV2): + args, err := decodeJupiterV6ExactOutRouteV2Arg(instruction.Data[8:]) + if err != nil { + return nil, err + } + exactOut = true + wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.QuotedIn, args.Out, args.RoutePlan) + case bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2): + args, err := decodeJupiterV6SharedAccountsExactOutRouteV2Arg(instruction.Data[8:]) + if err != nil { + return nil, err + } + exactOut = true + wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.QuotedIn, args.Out, args.RoutePlan) case bytes.Equal(disc, jupiterRoute): args, err := decodeJupiterV6RouteArg(instruction.Data[8:]) if err != nil { @@ -904,6 +1018,14 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( } _ = args inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) + wrapped, wrappedCnt = pumpWrappedAtIdx0(args.In, args.QuotedOut, args.Plan) + case bytes.Equal(disc, jupiterSharedAccountsExactOutRoute): + args, err := decodeJupiterV6SharedAccountsExactOutRouteArg(instruction.Data[8:]) + if err != nil { + return nil, err + } + exactOut = true + wrapped, wrappedCnt = pumpWrappedAtIdx0(args.QuotedIn, args.Out, args.Plan) case bytes.Equal(disc, jupiterSharedAccountsRoute): args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:]) if err != nil { @@ -911,9 +1033,62 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( } _ = args inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) + wrapped, wrappedCnt = pumpWrappedAtIdx0(args.In, args.QuotedOut, args.Plan) default: return nil, nil } + if wrappedCnt > 1 { + logger.Warn("pumpWrapped at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", wrappedCnt) + } + if wrapped.InAmount > 0 { + mint, ok, err := findPumpFunMint(tx.Message.StaticAccountKeys, instruction.Accounts) + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + event := "sell" + exactSol := false + var ( + token0AmountUint64 uint64 + token1AmountUint64 uint64 + ) + if wrapped.IsBuy { + event = "buy" + exactSol = !exactOut + token0AmountUint64 = wrapped.OutAmount + token1AmountUint64 = wrapped.InAmount + } else { + exactSol = exactOut && wrapped.OutAmount > 0 + token0AmountUint64 = wrapped.InAmount + token1AmountUint64 = wrapped.OutAmount + } + token0Amount := decimal.Zero + if token0AmountUint64 > 0 { + token0Amount = formatTokenAmount(token0AmountUint64) + } + token1Amount := decimal.Zero + if token1AmountUint64 > 0 { + token1Amount = formatSolAmount(token1AmountUint64) + } + return &TxSignal{ + TxHash: tx.Signatures[0].String(), + Maker: tx.Message.StaticAccountKeys[0].String(), + Token0Address: mint.String(), + Token1Address: wsolMint, + Token0Amount: token0Amount, + Token1Amount: token1Amount, + Program: "Pump", + Event: event, + IsToken2022: false, + IsMayhemMode: false, + ExactSOL: exactSol, + Block: tx.Block, + Token0AmountUint64: token0AmountUint64, + Token1AmountUint64: token1AmountUint64, + }, nil + } if planCount > 1 { // multiple pumpSwapSell at inputIdx=0? should not happen logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", planCount) diff --git a/pkg/shreder/txparser_test.go b/pkg/shreder/txparser_test.go index a19589a..3954b7e 100644 --- a/pkg/shreder/txparser_test.go +++ b/pkg/shreder/txparser_test.go @@ -289,3 +289,83 @@ func TestParsePhotonBuy(t *testing.T) { t.Fatalf("expected token1 amount 1955555553, got %d", signal.Token1AmountUint64) } } + +func TestParseJupiterV6PumpFunBuy(t *testing.T) { + rpcUrl := os.Getenv("SOL_RPC_URL") + if rpcUrl == "" { + t.Fatalf("SOL_RPC_URL is not set") + } + + client := rpc.New(rpcUrl) + signals := ParseTransaction( + getTransaction(t, client, "4QF5whXwjx234fMXeH3HrJCy5knFJmKPtgbXys8xKGz1pZypqPvXBr4BoAqXfYn8jLL4HXPY1pcvxCCW1XREFNxd"), + nil, + false, + ) + if len(signals) != 1 { + t.Fatalf("expected 1 signal, got %d", len(signals)) + } + + signal := signals[0] + if signal.Label != "jupiterv6" { + t.Fatalf("expected jupiterv6 signal, got %s", signal.Label) + } + if signal.Event != "buy" { + t.Fatalf("expected buy event, got %s", signal.Event) + } + if signal.Maker != "92ySgsZs3rsrUAq2aeEqYacXQQGmz6e4xHPrRGxLDJXb" { + t.Fatalf("expected maker 92ySgsZs3rsrUAq2aeEqYacXQQGmz6e4xHPrRGxLDJXb, got %s", signal.Maker) + } + if signal.Token0Address != "5kSWidFwDKPZiNf52TfincpVn8ufvkAfEzZ9pk8Dpump" { + t.Fatalf("expected token0 address 5kSWidFwDKPZiNf52TfincpVn8ufvkAfEzZ9pk8Dpump, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 2410530637576 { + t.Fatalf("expected token0 amount 2410530637576, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 380000000 { + t.Fatalf("expected token1 amount 380000000, got %d", signal.Token1AmountUint64) + } + if !signal.ExactSOL { + t.Fatalf("expected ExactSOL true, got false") + } +} + +func TestParseJupiterV6PumpFunSell(t *testing.T) { + rpcUrl := os.Getenv("SOL_RPC_URL") + if rpcUrl == "" { + t.Fatalf("SOL_RPC_URL is not set") + } + + client := rpc.New(rpcUrl) + signals := ParseTransaction( + getTransaction(t, client, "yCnE7ZA8dqB5iAZtwpSN2ar5HXh3gBjgaG2xtnwXDPFyHAm5XFU8642uTZTH5A2iPQ6G9hrj5eEPAJiWrfe38gM"), + nil, + false, + ) + if len(signals) != 1 { + t.Fatalf("expected 1 signal, got %d", len(signals)) + } + + signal := signals[0] + if signal.Label != "jupiterv6" { + t.Fatalf("expected jupiterv6 signal, got %s", signal.Label) + } + if signal.Event != "sell" { + t.Fatalf("expected sell event, got %s", signal.Event) + } + if signal.Maker != "CGfWcKKcVQNBCL1vpxXdg6rvfYpQmnS3WkyA22Lk5XnZ" { + t.Fatalf("expected maker CGfWcKKcVQNBCL1vpxXdg6rvfYpQmnS3WkyA22Lk5XnZ, got %s", signal.Maker) + } + if signal.Token0Address != "wp8Mwxy7btAD9hNWsfJyoPNJnjXS9fuNG4mnhQZpump" { + t.Fatalf("expected token0 address wp8Mwxy7btAD9hNWsfJyoPNJnjXS9fuNG4mnhQZpump, got %s", signal.Token0Address) + } + if signal.Token0AmountUint64 != 127531720509990 { + t.Fatalf("expected token0 amount 127531720509990, got %d", signal.Token0AmountUint64) + } + if signal.Token1AmountUint64 != 5296451290 { + t.Fatalf("expected token1 amount 5296451290, got %d", signal.Token1AmountUint64) + } + if signal.ExactSOL { + t.Fatalf("expected ExactSOL false, got true") + } +}