From 2d3f46ebbf25de80fda164546732c08484e67318 Mon Sep 17 00:00:00 2001 From: thloyi Date: Wed, 7 Jan 2026 11:18:02 +0800 Subject: [PATCH] juptierv6 --- pkg/shreder/addresstables.go | 21 ++++--- pkg/shreder/juptierv6.go | 119 ++++++++++++++++++++++++++++++----- pkg/shreder/txparser.go | 15 ++--- 3 files changed, 123 insertions(+), 32 deletions(-) diff --git a/pkg/shreder/addresstables.go b/pkg/shreder/addresstables.go index df8ff7b..741f07b 100644 --- a/pkg/shreder/addresstables.go +++ b/pkg/shreder/addresstables.go @@ -14,6 +14,7 @@ import ( type AddressTables struct { rpcClient *rpc.Client mux sync.RWMutex + loadMux sync.Mutex tables *lru.Cache[solana.PublicKey, []solana.PublicKey] loading map[solana.PublicKey]struct{} @@ -60,32 +61,34 @@ func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uin if !ok { at.mux.RUnlock() _ = at.pool.Submit(func() { - at.mux.RLock() + at.loadMux.Lock() _, loading := at.loading[tablePubkey] if loading { - at.mux.RUnlock() + at.loadMux.Unlock() return } - at.mux.RUnlock() - at.mux.Lock() at.loading[tablePubkey] = struct{}{} - at.mux.Unlock() + at.loadMux.Unlock() table, err := at.loadAddressTable(tablePubkey) if err != nil { logger.Error("loadAddressTable failed", "err", err, "table", tablePubkey) - at.mux.Lock() + at.loadMux.Lock() delete(at.loading, tablePubkey) - at.mux.Unlock() + at.loadMux.Unlock() return } + at.loadMux.Lock() + delete(at.loading, tablePubkey) + at.loadMux.Unlock() + at.mux.Lock() at.tables.Add(tablePubkey, table) total := at.tables.Len() - delete(at.loading, tablePubkey) at.mux.Unlock() logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total) }) + return nil } at.mux.RUnlock() @@ -93,6 +96,8 @@ func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uin var result solana.PublicKeySlice = make([]solana.PublicKey, 0, len(idx)) for _, i := range idx { if int(i) >= len(addresses) { + logger.Error("over loadAddressTable failed", "idx", i, "table", tablePubkey) + //todo... update table? continue } result = append(result, addresses[i]) diff --git a/pkg/shreder/juptierv6.go b/pkg/shreder/juptierv6.go index 53244ba..8a1c1ff 100644 --- a/pkg/shreder/juptierv6.go +++ b/pkg/shreder/juptierv6.go @@ -819,26 +819,43 @@ func decodeJupiterV6SharedAccountsRouteV2Arg(data []byte) (*JupiterV6SharedAccou return &JupiterV6SharedAccountsRouteV2Arg{ID: id, In: inAmt, QuotedOut: quotedOut, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil } -func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) uint64 { - var ret uint64 +func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) (uint64, int) { + var ( + ret uint64 + i int + ) for _, step := range plan { if step.InputIdx == 0 && (step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) { + i++ + if ret > 0 { + // multiple pumpSwapSell at inputIdx=0? should not happen + return 0, i + } ret += amount * uint64(step.Percent) / 100 } } - return ret + return ret, i } -func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) uint64 { - var ret uint64 +func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) { + var ( + ret uint64 + i int + ) for _, step := range plan { if step.InputIdx == 0 && (step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) { + i++ + if ret > 0 { + // multiple pumpSwapSell at inputIdx=0? should not happen + + return 0, i + } ret += amount * uint64(step.Bps) / 10000 } } - return ret + return ret, i } // only decodes inputIdx = 0 container pumpSwap instructions for now @@ -861,6 +878,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( var ( sourceMint solana.PublicKey inputAmount uint64 + planCount int err error ) @@ -872,34 +890,34 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( if err != nil { return nil, err } - inputAmount = pumpSwapSellAtIdx0V2(args.In, args.Plan) - + inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan) case bytes.Equal(disc, jupiterSharedAccountsRouteV2): args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:]) if err != nil { return nil, err } - inputAmount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan) - + inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan) case bytes.Equal(disc, jupiterRoute): args, err := decodeJupiterV6RouteArg(instruction.Data[8:]) if err != nil { return nil, err } _ = args - inputAmount = pumpSwapSellAtIdx0(args.In, args.Plan) - + inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) case bytes.Equal(disc, jupiterSharedAccountsRoute): args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:]) if err != nil { return nil, err } _ = args - inputAmount = pumpSwapSellAtIdx0(args.In, args.Plan) - + inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) default: return nil, 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) + } if inputAmount == 0 { return nil, nil } @@ -913,6 +931,40 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( if err != nil { return nil, err } + + var ( + srcIdx uint8 + ) + for i, acctIdx := range instruction.Accounts { + if i < 9 { + continue + } + key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + if err != nil { + return nil, err + } + if key.Equals(pumpAmmProgramID) { + srcIdx = uint8(i + 4) + break + } + } + if srcIdx == 0 { + return nil, nil + } + + sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx])) + if err != nil { + return nil, err + } + + quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) + if err != nil { + return nil, err + } + if !quoteMint.Equals(solana.WrappedSol) { + return nil, nil + } + } else if bytes.Equal(disc, jupiterSharedAccountsRoute) { if len(instruction.Accounts) < 12 { return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterSharedAccountsRoute instruction") @@ -921,6 +973,38 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( if err != nil { return nil, err } + var ( + srcIdx uint8 + ) + for i, acctIdx := range instruction.Accounts { + if i < 12 { + continue + } + key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + if err != nil { + return nil, err + } + if key.Equals(pumpAmmProgramID) { + srcIdx = uint8(i + 4) + break + } + } + if srcIdx == 0 { + return nil, nil + } + + sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx])) + if err != nil { + return nil, err + } + + quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) + if err != nil { + return nil, err + } + if !quoteMint.Equals(solana.WrappedSol) { + return nil, nil + } } else { if len(instruction.Accounts) < 10 { return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterRoute instruction") @@ -930,7 +1014,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( ) for i, acctIdx := range instruction.Accounts { - if i <= 9 { + if i < 9 { continue } key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) @@ -949,11 +1033,12 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( if err != nil { return nil, err } - distMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) + + quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) if err != nil { return nil, err } - if !distMint.Equals(solana.WrappedSol) { + if !quoteMint.Equals(solana.WrappedSol) { return nil, nil } } diff --git a/pkg/shreder/txparser.go b/pkg/shreder/txparser.go index 5efb0ff..15d3281 100644 --- a/pkg/shreder/txparser.go +++ b/pkg/shreder/txparser.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "math/big" + "strings" "github.com/gagliardetto/solana-go" "github.com/mr-tron/base58" @@ -238,6 +239,7 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) staticKeys = append(staticKeys, accounts...) } } + versioned.Message.StaticAccountKeys = staticKeys } var parsed []*TxSignal @@ -280,9 +282,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) case terminalProgramID: txRes, err := parseTermInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "terminal") - //case jupiterV6ProgramID: - // txRes, err := parseJupiterV6Instruction(versioned, i) - // parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6") + case jupiterV6ProgramID: + txRes, err := parseJupiterV6Instruction(versioned, i) + parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6") } } @@ -291,10 +293,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal { if err != nil { - //if errors.Is(err, &AccountNotFoundError{}) { - // - //} - logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) + if !strings.HasPrefix(err.Error(), "account index") { + logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) + } return list } if parsed != nil {