package shreder import ( "bytes" "encoding/binary" "fmt" bin "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) var ( jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4") jupiterRouteV2 = []byte{187, 100, 250, 204, 49, 196, 175, 20} jupiterExactOutRouteV2 = []byte{157, 138, 184, 82, 21, 244, 243, 36} jupiterRoute = []byte{229, 23, 203, 151, 122, 227, 173, 42} jupiterRouteWithTokenLedger = []byte{150, 86, 71, 116, 167, 93, 14, 104} jupiterSharedAccountsExactOutRoute = []byte{176, 209, 105, 168, 154, 125, 69, 62} jupiterSharedAccountsRoute = []byte{193, 32, 155, 51, 65, 214, 156, 129} jupiterSharedAccountsRouteWithTokenLedger = []byte{230, 121, 143, 80, 119, 159, 106, 170} jupiterSharedAccountsExactOutRouteV2 = []byte{53, 96, 229, 202, 216, 187, 250, 24} jupiterSharedAccountsRouteV2 = []byte{209, 152, 83, 147, 124, 254, 216, 233} usdcMint = solana.MustPublicKeyFromBase58("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") usd1Mint = solana.MustPublicKeyFromBase58("USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB") usdtMint = solana.MustPublicKeyFromBase58("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB") ) type Side uint8 type SwapKind uint8 const ( Saber SwapKind = iota SaberAddDecimalsDeposit SaberAddDecimalsWithdraw TokenSwap Sencha Step Cropper Raydium Crema Lifinity Mercurial Cykura Serum MarinadeDeposit MarinadeUnstake Aldrin AldrinV2 Whirlpool Invariant Meteora GooseFX DeltaFi Balansol MarcoPolo Dradex LifinityV2 RaydiumClmm Openbook Phoenix Symmetry TokenSwapV2 HeliumTreasuryManagementRedeemV0 StakeDexStakeWrappedSol StakeDexSwapViaStake GooseFXV2 Perps PerpsAddLiquidity PerpsRemoveLiquidity MeteoraDlmm OpenBookV2 RaydiumClmmV2 StakeDexPrefundWithdrawStakeAndDepositStake Clone SanctumS SanctumSAddLiquidity SanctumSRemoveLiquidity RaydiumCP WhirlpoolSwapV2 OneIntro PumpWrappedBuy PumpWrappedSell PerpsV2 PerpsV2AddLiquidity PerpsV2RemoveLiquidity MoonshotWrappedBuy MoonshotWrappedSell StabbleStableSwap StabbleWeightedSwap Obric FoxBuyFromEstimatedCost FoxClaimPartial SolFi SolayerDelegateNoInit SolayerUndelegateNoInit TokenMill DaosFunBuy DaosFunSell ZeroFi StakeDexWithdrawWrappedSol VirtualsBuy VirtualsSell Perena PumpSwapBuy PumpSwapSell Gamma MeteoraDlmmSwapV2 Woofi MeteoraDammV2 MeteoraDynamicBondingCurveSwap StabbleStableSwapV2 StabbleWeightedSwapV2 RaydiumLaunchlabBuy RaydiumLaunchlabSell BoopdotfunWrappedBuy BoopdotfunWrappedSell Plasma GoonFi HumidiFi MeteoraDynamicBondingCurveSwapWithRemainingAccounts TesseraV PumpWrappedBuyV2 PumpWrappedSellV2 PumpSwapBuyV2 PumpSwapSellV2 Heaven SolFiV2 Aquifer PumpWrappedBuyV3 PumpWrappedSellV3 PumpSwapBuyV3 PumpSwapSellV3 JupiterLendDeposit JupiterLendRedeem DefiTuna AlphaQ RaydiumV2 SarosDlmm Futarchy MeteoraDammV2WithRemainingAccounts Obsidian WhaleStreet DynamicV1 PumpWrappedBuyV4 PumpWrappedSellV4 CarrotIssue CarrotRedeem Manifest BisonFi HumidiFiV2 PerenaStar JupiterRfqV2 GoonFiV2 ) var swapKindNames = [122]string{"Saber", "SaberAddDecimalsDeposit", "SaberAddDecimalsWithdraw", "TokenSwap", "Sencha", "Step", "Cropper", "Raydium", "Crema", "Lifinity", "Mercurial", "Cykura", "Serum", "MarinadeDeposit", "MarinadeUnstake", "Aldrin", "AldrinV2", "Whirlpool", "Invariant", "Meteora", "GooseFX", "DeltaFi", "Balansol", "MarcoPolo", "Dradex", "LifinityV2", "RaydiumClmm", "Openbook", "Phoenix", "Symmetry", "TokenSwapV2", "HeliumTreasuryManagementRedeemV0", "StakeDexStakeWrappedSol", "StakeDexSwapViaStake", "GooseFXV2", "Perps", "PerpsAddLiquidity", "PerpsRemoveLiquidity", "MeteoraDlmm", "OpenBookV2", "RaydiumClmmV2", "StakeDexPrefundWithdrawStakeAndDepositStake", "Clone", "SanctumS", "SanctumSAddLiquidity", "SanctumSRemoveLiquidity", "RaydiumCP", "WhirlpoolSwapV2", "OneIntro", "PumpWrappedBuy", "PumpWrappedSell", "PerpsV2", "PerpsV2AddLiquidity", "PerpsV2RemoveLiquidity", "MoonshotWrappedBuy", "MoonshotWrappedSell", "StabbleStableSwap", "StabbleWeightedSwap", "Obric", "FoxBuyFromEstimatedCost", "FoxClaimPartial", "SolFi", "SolayerDelegateNoInit", "SolayerUndelegateNoInit", "TokenMill", "DaosFunBuy", "DaosFunSell", "ZeroFi", "StakeDexWithdrawWrappedSol", "VirtualsBuy", "VirtualsSell", "Perena", "PumpSwapBuy", "PumpSwapSell", "Gamma", "MeteoraDlmmSwapV2", "Woofi", "MeteoraDammV2", "MeteoraDynamicBondingCurveSwap", "StabbleStableSwapV2", "StabbleWeightedSwapV2", "RaydiumLaunchlabBuy", "RaydiumLaunchlabSell", "BoopdotfunWrappedBuy", "BoopdotfunWrappedSell", "Plasma", "GoonFi", "HumidiFi", "MeteoraDynamicBondingCurveSwapWithRemainingAccounts", "TesseraV", "PumpWrappedBuyV2", "PumpWrappedSellV2", "PumpSwapBuyV2", "PumpSwapSellV2", "Heaven", "SolFiV2", "Aquifer", "PumpWrappedBuyV3", "PumpWrappedSellV3", "PumpSwapBuyV3", "PumpSwapSellV3", "JupiterLendDeposit", "JupiterLendRedeem", "DefiTuna", "AlphaQ", "RaydiumV2", "SarosDlmm", "Futarchy", "MeteoraDammV2WithRemainingAccounts", "Obsidian", "WhaleStreet", "DynamicV1", "PumpWrappedBuyV4", "PumpWrappedSellV4", "CarrotIssue", "CarrotRedeem", "Manifest", "BisonFi", "HumidiFiV2", "PerenaStar", "JupiterRfqV2", "GoonFiV2"} func (s SwapKind) String() string { idx := int(s) if idx < 0 || idx >= len(swapKindNames) { return fmt.Sprintf("SwapKind(%d)", uint8(s)) } return swapKindNames[idx] } type Swap struct { Kind SwapKind } type RoutePlanStepV2 struct { Swap Swap Bps uint16 InputIdx uint8 OutputIdx uint8 } type RoutePlanStep struct { Swap Swap Percent uint8 InputIdx uint8 OutputIdx uint8 } type JupiterV6RouteV2Arg struct { In uint64 Out uint64 SlippageBps uint16 PlatformFeeBps uint16 PositiveSlippageBps uint16 Plan []RoutePlanStepV2 } func skipRemainingAccountsInfo(dec *bin.Decoder) error { // RemainingAccountsInfo { slices: Vec } // RemainingAccountsSlice { accounts_type: u8, length: u8 } ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return err } // each slice is 2 bytes return dec.SkipBytes(uint(ln) * 2) } func skipOptionRemainingAccountsInfo(dec *bin.Decoder) error { pos := dec.Position() tag, err := dec.ReadUint8() if err != nil { return err } switch tag { case 0: return nil case 1: return skipRemainingAccountsInfo(dec) default: // Version drift: sometimes a swap variant we think has Option is actually no-payload. _ = dec.SetPosition(pos) return nil } } func skipCandidateSwap(dec *bin.Decoder) error { // CandidateSwap enum (this IDL variant): // 0 HumidiFi { u64, bool } // 1 TesseraV { Side(u8) } // NOTE: other IDL versions may include more variants (e.g. HumidiFiV2). tag, err := dec.ReadUint8() if err != nil { return err } switch tag { case 0: // HumidiFi u64 + bool if err := dec.SkipBytes(8); err != nil { return err } return dec.SkipBytes(1) case 1: // TesseraV (Side: u8) return dec.SkipBytes(1) case 2: // Seen in other IDLs: HumidiFiV2 { u64, bool } if err := dec.SkipBytes(8); err != nil { return err } return dec.SkipBytes(1) default: return fmt.Errorf("unknown CandidateSwap variant: %d", tag) } } func skipDynamicV1(dec *bin.Decoder) error { // DynamicV1 { candidate_swaps: Vec } ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return err } for i := uint32(0); i < ln; i++ { if err := skipCandidateSwap(dec); err != nil { return fmt.Errorf("CandidateSwap[%d]: %w", i, err) } } return nil } func decodeSwap(dec *bin.Decoder) (Swap, error) { tag, err := dec.ReadUint8() if err != nil { return Swap{}, fmt.Errorf("read Swap variant: %w", err) } k := SwapKind(tag) out := Swap{Kind: k} skipU8 := func() error { return dec.SkipBytes(1) } skipBool := func() error { return dec.SkipBytes(1) } skipU32 := func() error { return dec.SkipBytes(4) } skipU64 := func() error { return dec.SkipBytes(8) } skipTwoU64 := func() error { return dec.SkipBytes(16) } skipRemaining := func() error { return skipRemainingAccountsInfo(dec) } skipOptRemaining := func() error { return skipOptionRemainingAccountsInfo(dec) } switch k { // -------- payload-less variants -------- case Saber, SaberAddDecimalsDeposit, SaberAddDecimalsWithdraw, TokenSwap, Sencha, Step, Cropper, Raydium, Lifinity, Mercurial, Cykura, MarinadeDeposit, MarinadeUnstake, Meteora, GooseFX, Balansol, LifinityV2, RaydiumClmm, TokenSwapV2, HeliumTreasuryManagementRedeemV0, StakeDexStakeWrappedSol, GooseFXV2, Perps, PerpsAddLiquidity, PerpsRemoveLiquidity, MeteoraDlmm, RaydiumClmmV2, RaydiumCP, OneIntro, PumpWrappedBuy, PumpWrappedSell, PerpsV2, PerpsV2AddLiquidity, PerpsV2RemoveLiquidity, MoonshotWrappedBuy, MoonshotWrappedSell, StabbleStableSwap, StabbleWeightedSwap, FoxBuyFromEstimatedCost, SolayerDelegateNoInit, SolayerUndelegateNoInit, DaosFunBuy, DaosFunSell, ZeroFi, StakeDexWithdrawWrappedSol, VirtualsBuy, VirtualsSell, PumpSwapBuy, PumpSwapSell, Gamma, Woofi, MeteoraDammV2, MeteoraDynamicBondingCurveSwap, StabbleStableSwapV2, StabbleWeightedSwapV2, BoopdotfunWrappedBuy, BoopdotfunWrappedSell, MeteoraDynamicBondingCurveSwapWithRemainingAccounts, PumpWrappedBuyV2, PumpWrappedSellV2, PumpSwapBuyV2, PumpSwapSellV2, Aquifer, PumpWrappedBuyV3, PumpWrappedSellV3, PumpSwapBuyV3, PumpSwapSellV3, JupiterLendDeposit, JupiterLendRedeem, RaydiumV2, MeteoraDammV2WithRemainingAccounts, Obsidian, PumpWrappedBuyV4, PumpWrappedSellV4, CarrotIssue, CarrotRedeem: return out, nil // -------- bool payload -------- case Crema, Whirlpool, Invariant, DeltaFi, MarcoPolo, Obric, FoxClaimPartial, SolFi, Heaven, SolFiV2, AlphaQ, SarosDlmm, BisonFi, PerenaStar, GoonFiV2: return out, skipBool() // -------- u32 -------- case StakeDexSwapViaStake, StakeDexPrefundWithdrawStakeAndDepositStake: return out, skipU32() // -------- u64 -------- case RaydiumLaunchlabBuy, RaydiumLaunchlabSell: return out, skipU64() // -------- Side(u8) payload -------- case Serum, Aldrin, AldrinV2, Dradex, Openbook, Phoenix, OpenBookV2, TokenMill, Plasma, TesseraV, Futarchy, WhaleStreet, Manifest: return out, skipU8() // -------- MeteoraDlmmSwapV2: RemainingAccountsInfo -------- case MeteoraDlmmSwapV2: return out, skipRemaining() // -------- DynamicV1: Vec -------- case DynamicV1: return out, skipDynamicV1(dec) // -------- u64 + u64 -------- case Symmetry: return out, skipTwoU64() // -------- Clone: u8 + bool + bool -------- case Clone: if err := skipU8(); err != nil { return Swap{}, err } if err := skipBool(); err != nil { return Swap{}, err } return out, skipBool() // -------- SanctumS: u8 + u8 + u32 + u32 -------- case SanctumS: if err := skipU8(); err != nil { return Swap{}, err } if err := skipU8(); err != nil { return Swap{}, err } if err := skipU32(); err != nil { return Swap{}, err } return out, skipU32() // -------- SanctumS(Add/Remove)Liquidity: u8 + u32 -------- case SanctumSAddLiquidity, SanctumSRemoveLiquidity: if err := skipU8(); err != nil { return Swap{}, err } return out, skipU32() // -------- WhirlpoolSwapV2 / DefiTuna: bool + Option -------- case WhirlpoolSwapV2, DefiTuna: if err := skipBool(); err != nil { return Swap{}, err } return out, skipOptRemaining() // -------- Perena: u8 + u8 -------- case Perena: if err := skipU8(); err != nil { return Swap{}, err } return out, skipU8() case GoonFi: if err := skipBool(); err != nil { return Swap{}, err } return out, skipU8() // -------- HumidiFi/HumidiFiV2: u64 + bool -------- case HumidiFi, HumidiFiV2: if err := skipU64(); err != nil { return Swap{}, err } return out, skipBool() // -------- JupiterRfqV2: Side(u8) + bytes -------- case JupiterRfqV2: // side if err := skipU8(); err != nil { return Swap{}, err } ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return Swap{}, err } if err := dec.SkipBytes(uint(ln)); err != nil { return Swap{}, err } return out, nil default: // Unknown/new variant: assume no payload (keeps decoder aligned for RoutePlanStepV2 if it really is no-payload). return out, nil } } func decodeRoutePlanStepV2(dec *bin.Decoder) (RoutePlanStepV2, error) { sw, err := decodeSwap(dec) if err != nil { return RoutePlanStepV2{}, err } bps, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return RoutePlanStepV2{}, err } inIdx, err := dec.ReadUint8() if err != nil { return RoutePlanStepV2{}, err } outIdx, err := dec.ReadUint8() if err != nil { return RoutePlanStepV2{}, err } return RoutePlanStepV2{Swap: sw, Bps: bps, InputIdx: inIdx, OutputIdx: outIdx}, nil } func decodeRoutePlanStep(dec *bin.Decoder) (RoutePlanStep, error) { sw, err := decodeSwap(dec) if err != nil { return RoutePlanStep{}, err } percent, err := dec.ReadUint8() if err != nil { return RoutePlanStep{}, err } inIdx, err := dec.ReadUint8() if err != nil { return RoutePlanStep{}, err } outIdx, err := dec.ReadUint8() if err != nil { return RoutePlanStep{}, err } return RoutePlanStep{Swap: sw, Percent: percent, InputIdx: inIdx, OutputIdx: outIdx}, nil } func decodeJupiterV6RouteV2Arg(data []byte) (*JupiterV6RouteV2Arg, error) { dec := bin.NewBorshDecoder(data) var err error out := &JupiterV6RouteV2Arg{} if out.In, err = dec.ReadUint64(binary.LittleEndian); err != nil { return nil, err } if out.Out, err = dec.ReadUint64(binary.LittleEndian); err != nil { return nil, err } if out.SlippageBps, err = dec.ReadUint16(binary.LittleEndian); err != nil { return nil, err } if out.PlatformFeeBps, err = dec.ReadUint16(binary.LittleEndian); err != nil { return nil, err } if out.PositiveSlippageBps, err = dec.ReadUint16(binary.LittleEndian); err != nil { return nil, err } // vec: u32 length + elements ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return nil, err } out.Plan = make([]RoutePlanStepV2, 0, ln) for i := uint32(0); i < ln; i++ { step, err := decodeRoutePlanStepV2(dec) if err != nil { return nil, fmt.Errorf("decode Plan[%d]: %w", i, err) } out.Plan = append(out.Plan, step) } return out, nil } type JupiterV6RouteArg struct { Plan []RoutePlanStep In uint64 QuotedOut uint64 SlippageBps uint16 PlatformFeeBps uint8 } type JupiterV6RouteWithTokenLedgerArg struct { Plan []RoutePlanStep QuotedOut uint64 SlippageBps uint16 PlatformFeeBps uint8 } type JupiterV6SharedAccountsExactOutRouteArg struct { ID uint8 Plan []RoutePlanStep Out uint64 QuotedIn uint64 SlippageBps uint16 PlatformFeeBps uint8 } type JupiterV6SharedAccountsRouteArg struct { ID uint8 Plan []RoutePlanStep In uint64 QuotedOut uint64 SlippageBps uint16 PlatformFeeBps uint8 } type JupiterV6SharedAccountsRouteWithTokenLedgerArg struct { ID uint8 Plan []RoutePlanStep QuotedOut uint64 SlippageBps uint16 PlatformFeeBps uint8 } type JupiterV6ExactOutRouteV2Arg struct { Out uint64 QuotedIn uint64 Slippage uint16 PlatFee uint16 PosSlip uint16 RoutePlan []RoutePlanStepV2 } type JupiterV6SharedAccountsExactOutRouteV2Arg struct { ID uint8 Out uint64 QuotedIn uint64 Slippage uint16 PlatFee uint16 PosSlip uint16 RoutePlan []RoutePlanStepV2 } type JupiterV6SharedAccountsRouteV2Arg struct { ID uint8 In uint64 QuotedOut uint64 Slippage uint16 PlatFee uint16 PosSlip uint16 RoutePlan []RoutePlanStepV2 } func decodeVecRoutePlanStep(dec *bin.Decoder) ([]RoutePlanStep, error) { ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return nil, err } out := make([]RoutePlanStep, 0, ln) for i := uint32(0); i < ln; i++ { step, err := decodeRoutePlanStep(dec) if err != nil { return nil, fmt.Errorf("decode RoutePlanStep[%d]: %w", i, err) } out = append(out, step) } return out, nil } func decodeVecRoutePlanStepV2(dec *bin.Decoder) ([]RoutePlanStepV2, error) { ln, err := dec.ReadUint32(binary.LittleEndian) if err != nil { return nil, err } out := make([]RoutePlanStepV2, 0, ln) for i := uint32(0); i < ln; i++ { step, err := decodeRoutePlanStepV2(dec) if err != nil { return nil, fmt.Errorf("decode RoutePlanStepV2[%d]: %w", i, err) } out = append(out, step) } return out, nil } func decodeJupiterV6RouteArg(data []byte) (*JupiterV6RouteArg, error) { dec := bin.NewBorshDecoder(data) plan, err := decodeVecRoutePlanStep(dec) if err != nil { return nil, err } in, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } quotedOut, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint8() if err != nil { return nil, err } return &JupiterV6RouteArg{Plan: plan, In: in, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil } func decodeJupiterV6RouteWithTokenLedgerArg(data []byte) (*JupiterV6RouteWithTokenLedgerArg, error) { dec := bin.NewBorshDecoder(data) plan, err := decodeVecRoutePlanStep(dec) if err != nil { return nil, err } quotedOut, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint8() if err != nil { return nil, err } return &JupiterV6RouteWithTokenLedgerArg{Plan: plan, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil } func decodeJupiterV6SharedAccountsExactOutRouteArg(data []byte) (*JupiterV6SharedAccountsExactOutRouteArg, error) { dec := bin.NewBorshDecoder(data) id, err := dec.ReadUint8() if err != nil { return nil, err } plan, err := decodeVecRoutePlanStep(dec) if err != nil { return nil, err } outAmt, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } quotedIn, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint8() if err != nil { return nil, err } return &JupiterV6SharedAccountsExactOutRouteArg{ID: id, Plan: plan, Out: outAmt, QuotedIn: quotedIn, SlippageBps: slippage, PlatformFeeBps: pf}, nil } func decodeJupiterV6SharedAccountsRouteArg(data []byte) (*JupiterV6SharedAccountsRouteArg, error) { dec := bin.NewBorshDecoder(data) id, err := dec.ReadUint8() if err != nil { return nil, err } plan, err := decodeVecRoutePlanStep(dec) if err != nil { return nil, err } inAmt, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } quotedOut, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint8() if err != nil { return nil, err } return &JupiterV6SharedAccountsRouteArg{ID: id, Plan: plan, In: inAmt, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil } func decodeJupiterV6SharedAccountsRouteWithTokenLedgerArg(data []byte) (*JupiterV6SharedAccountsRouteWithTokenLedgerArg, error) { dec := bin.NewBorshDecoder(data) id, err := dec.ReadUint8() if err != nil { return nil, err } plan, err := decodeVecRoutePlanStep(dec) if err != nil { return nil, err } quotedOut, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint8() if err != nil { return nil, err } return &JupiterV6SharedAccountsRouteWithTokenLedgerArg{ID: id, Plan: plan, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil } func decodeJupiterV6ExactOutRouteV2Arg(data []byte) (*JupiterV6ExactOutRouteV2Arg, error) { dec := bin.NewBorshDecoder(data) outAmt, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } quotedIn, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pos, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } plan, err := decodeVecRoutePlanStepV2(dec) if err != nil { return nil, err } return &JupiterV6ExactOutRouteV2Arg{Out: outAmt, QuotedIn: quotedIn, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil } func decodeJupiterV6SharedAccountsExactOutRouteV2Arg(data []byte) (*JupiterV6SharedAccountsExactOutRouteV2Arg, error) { dec := bin.NewBorshDecoder(data) id, err := dec.ReadUint8() if err != nil { return nil, err } outAmt, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } quotedIn, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pos, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } plan, err := decodeVecRoutePlanStepV2(dec) if err != nil { return nil, err } return &JupiterV6SharedAccountsExactOutRouteV2Arg{ID: id, Out: outAmt, QuotedIn: quotedIn, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil } func decodeJupiterV6SharedAccountsRouteV2Arg(data []byte) (*JupiterV6SharedAccountsRouteV2Arg, error) { dec := bin.NewBorshDecoder(data) id, err := dec.ReadUint8() if err != nil { return nil, err } inAmt, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } quotedOut, err := dec.ReadUint64(binary.LittleEndian) if err != nil { return nil, err } slippage, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pf, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } pos, err := dec.ReadUint16(binary.LittleEndian) if err != nil { return nil, err } plan, err := decodeVecRoutePlanStepV2(dec) if err != nil { return nil, err } return &JupiterV6SharedAccountsRouteV2Arg{ID: id, In: inAmt, QuotedOut: quotedOut, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil } func isInputIdx0(idx uint8) bool { return idx == 0 } func isPumpSwapSellKind(kind SwapKind) bool { switch kind { case PumpSwapSell, PumpSwapSellV2, PumpSwapSellV3: return true default: return false } } func isPumpSwapBuyKind(kind SwapKind) bool { switch kind { case PumpSwapBuy, PumpSwapBuyV2, PumpSwapBuyV3: return true default: return false } } func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) (uint64, int) { var ( ret uint64 i int ) for _, step := range plan { if !isInputIdx0(step.InputIdx) || !isPumpSwapSellKind(step.Swap.Kind) { continue } i++ if ret > 0 { // multiple pumpSwapSell at inputIdx=0? should not happen return 0, i } ret += amount * uint64(step.Percent) / 100 } return ret, i } func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) { var ( ret uint64 i int ) for _, step := range plan { if !isInputIdx0(step.InputIdx) || !isPumpSwapSellKind(step.Swap.Kind) { continue } i++ if ret > 0 { // multiple pumpSwapSell at inputIdx=0? should not happen return 0, i } ret += amount * uint64(step.Bps) / 10000 } return ret, i } type pumpSwapBuyMatch struct { InAmount uint64 OutAmount uint64 } func pumpSwapBuyAtIdx0(in uint64, out uint64, plan []RoutePlanStep) (pumpSwapBuyMatch, int) { var ( ret pumpSwapBuyMatch count int ) for _, step := range plan { if !isInputIdx0(step.InputIdx) || !isPumpSwapBuyKind(step.Swap.Kind) { continue } count++ if count > 1 { return pumpSwapBuyMatch{}, count } ret.InAmount = in * uint64(step.Percent) / 100 if step.Percent == 100 { ret.OutAmount = out } } return ret, count } func pumpSwapBuyAtIdx0V2(in uint64, out uint64, plan []RoutePlanStepV2) (pumpSwapBuyMatch, int) { var ( ret pumpSwapBuyMatch count int ) for _, step := range plan { if !isInputIdx0(step.InputIdx) || !isPumpSwapBuyKind(step.Swap.Kind) { continue } count++ if count > 1 { return pumpSwapBuyMatch{}, count } ret.InAmount = in * uint64(step.Bps) / 10000 if step.Bps == 10000 { ret.OutAmount = out } } return ret, count } 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 isPumpWrappedKind(kind SwapKind) bool { return isPumpWrappedBuy(kind) || isPumpWrappedSell(kind) } func isStableMint(mint solana.PublicKey) bool { if mint.Equals(usdcMint) { return true } if mint.Equals(usd1Mint) { return true } if mint.Equals(usdtMint) { return true } return false } func isToken1Mint(mint solana.PublicKey) bool { return mint.Equals(solana.WrappedSol) || mint.Equals(solana.SystemProgramID) || isStableMint(mint) } func isJupiterV6Token1RequiredDisc(disc []byte) bool { return bytes.Equal(disc, jupiterRouteV2) || bytes.Equal(disc, jupiterSharedAccountsRouteV2) || bytes.Equal(disc, jupiterExactOutRouteV2) || bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2) || bytes.Equal(disc, jupiterSharedAccountsRoute) || bytes.Equal(disc, jupiterSharedAccountsExactOutRoute) } func pumpWrappedAtIdx0(in uint64, out uint64, plan []RoutePlanStep) (pumpWrappedMatch, int) { var ( ret pumpWrappedMatch count int ) for _, step := range plan { if !isInputIdx0(step.InputIdx) { continue } if !isPumpWrappedKind(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 !isInputIdx0(step.InputIdx) { continue } if !isPumpWrappedKind(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 pumpWrappedAny(plan []RoutePlanStep) (pumpWrappedMatch, int) { var ( ret pumpWrappedMatch count int ) for _, step := range plan { if !isPumpWrappedKind(step.Swap.Kind) { continue } count++ if count > 1 { return pumpWrappedMatch{}, count } ret.IsBuy = isPumpWrappedBuy(step.Swap.Kind) } return ret, count } func pumpWrappedAnyV2(plan []RoutePlanStepV2) (pumpWrappedMatch, int) { var ( ret pumpWrappedMatch count int ) for _, step := range plan { if !isPumpWrappedKind(step.Swap.Kind) { continue } count++ if count > 1 { return pumpWrappedMatch{}, count } ret.IsBuy = isPumpWrappedBuy(step.Swap.Kind) } return ret, count } func pumpRoutePlanStats(in uint64, out uint64, plan []RoutePlanStep, includeInput bool) (uint64, int, pumpSwapBuyMatch, int, pumpWrappedMatch, int, pumpWrappedMatch, int) { var ( inputAmount uint64 planCount int ) if includeInput { inputAmount, planCount = pumpSwapSellAtIdx0(in, plan) } buySwap, buySwapCnt := pumpSwapBuyAtIdx0(in, out, plan) wrapped, wrappedCnt := pumpWrappedAtIdx0(in, out, plan) wrappedAny, wrappedAnyC := pumpWrappedAny(plan) return inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC } func pumpRoutePlanStatsV2(in uint64, out uint64, plan []RoutePlanStepV2, includeInput bool) (uint64, int, pumpSwapBuyMatch, int, pumpWrappedMatch, int, pumpWrappedMatch, int) { var ( inputAmount uint64 planCount int ) if includeInput { inputAmount, planCount = pumpSwapSellAtIdx0V2(in, plan) } buySwap, buySwapCnt := pumpSwapBuyAtIdx0V2(in, out, plan) wrapped, wrappedCnt := pumpWrappedAtIdx0V2(in, out, plan) wrappedAny, wrappedAnyC := pumpWrappedAnyV2(plan) return inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC } func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions, in uint64, out uint64, plan []RoutePlanStep) (*TxSignal, bool, error) { var ( isBuy bool isSell bool count int ) for _, step := range plan { if !isInputIdx0(step.InputIdx) { continue } if isPumpSwapSellKind(step.Swap.Kind) { isSell = true count++ } else if isPumpSwapBuyKind(step.Swap.Kind) { isBuy = true count++ } } if count == 0 { return nil, false, nil } if count > 1 || (isBuy && isSell) { logger.Warn("pumpamm route at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", count) return nil, true, nil } if len(instruction.Accounts) < 14 { return nil, true, nil } token0Key, err := tx.GetAccount(int(instruction.Accounts[13])) if err != nil { return nil, true, err } if isSell { token0Amount := decimal.Zero if in > 0 { token0Amount = formatTokenAmount(in) } return &TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Token0Address: token0Key.String(), Token1Address: wsolMint, Token0Amount: token0Amount, Token1Amount: decimal.Zero, Program: "PumpAMM", Event: "sell", IsToken2022: false, IsMayhemMode: false, ExactSOL: false, Block: tx.Block, Token0AmountUint64: in, Token1AmountUint64: 0, }, true, nil } if len(instruction.Accounts) < 15 { return nil, true, nil } wsolKey, err := tx.GetAccount(int(instruction.Accounts[14])) if err != nil { return nil, true, err } if !wsolKey.Equals(solana.WrappedSol) { return nil, true, nil } token0Amount := decimal.Zero if out > 0 { token0Amount = formatTokenAmount(out) } token1Amount := decimal.Zero if in > 0 { token1Amount = formatSolAmount(in) } return &TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Token0Address: token0Key.String(), Token1Address: wsolMint, Token0Amount: token0Amount, Token1Amount: token1Amount, Program: "PumpAMM", Event: "buy", IsToken2022: false, IsMayhemMode: false, ExactSOL: true, Block: tx.Block, Token0AmountUint64: out, Token1AmountUint64: in, }, true, nil } func findPumpFunMint(tx VersionedTransaction, accounts []uint8) (solana.PublicKey, bool, error) { for i, acctIdx := range accounts { key, err := tx.GetAccount(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 := tx.GetAccount(int(accounts[i+3])) if err != nil { return solana.PublicKey{}, false, err } return mint, true, nil } return solana.PublicKey{}, false, nil } func jupiterV6SourceDestMints(msg VersionedTransaction, instruction Instructions, disc []byte) (solana.PublicKey, solana.PublicKey, bool, error) { switch { case bytes.Equal(disc, jupiterRouteV2), bytes.Equal(disc, jupiterSharedAccountsRouteV2), bytes.Equal(disc, jupiterExactOutRouteV2), bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2): if len(instruction.Accounts) < 5 { return solana.PublicKey{}, solana.PublicKey{}, false, fmt.Errorf("not enough accounts for jupiter v6 v2 instruction") } src, err := msg.GetAccount(int(instruction.Accounts[3])) if err != nil { return solana.PublicKey{}, solana.PublicKey{}, false, err } dst, err := msg.GetAccount(int(instruction.Accounts[4])) if err != nil { return solana.PublicKey{}, solana.PublicKey{}, false, err } return src, dst, true, nil case bytes.Equal(disc, jupiterSharedAccountsRoute), bytes.Equal(disc, jupiterSharedAccountsExactOutRoute): if len(instruction.Accounts) < 9 { return solana.PublicKey{}, solana.PublicKey{}, false, fmt.Errorf("not enough accounts for jupiter v6 shared accounts instruction") } src, err := msg.GetAccount(int(instruction.Accounts[7])) if err != nil { return solana.PublicKey{}, solana.PublicKey{}, false, err } dst, err := msg.GetAccount(int(instruction.Accounts[8])) if err != nil { return solana.PublicKey{}, solana.PublicKey{}, false, err } return src, dst, true, nil default: return solana.PublicKey{}, solana.PublicKey{}, false, nil } } // only decodes inputIdx = 0 container pumpSwap instructions for now func parseJupiterV6Instruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) { if instructionIndex >= len(tx.Instructions) { return nil, fmt.Errorf("instruction index out of bounds") } instruction := tx.Instructions[instructionIndex] if len(instruction.Data) == 0 { return nil, fmt.Errorf("data is empty") } if len(instruction.Data) < 8 { return nil, nil } disc := instruction.Data[:8] var ( sourceMint solana.PublicKey inputAmount uint64 routeIn uint64 routeOut uint64 planCount int buySwap pumpSwapBuyMatch buySwapCnt int wrapped pumpWrappedMatch wrappedCnt int wrappedAny pumpWrappedMatch wrappedAnyC int exactOut bool err error ) // route_v2 / exact_out_route_v2 / shared_accounts_*_v2 use accounts[3]/[4] as src/dst mints (per IDL) // route/shared_accounts_* (v1) use different account layouts; we only decode args here. switch { case bytes.Equal(disc, jupiterRouteV2): args, err := decodeJupiterV6RouteV2Arg(instruction.Data[8:]) if err != nil { return nil, err } inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.In, args.Out, args.Plan, true) routeIn = args.In routeOut = args.Out case bytes.Equal(disc, jupiterSharedAccountsRouteV2): args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:]) if err != nil { return nil, err } inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.In, args.QuotedOut, args.RoutePlan, true) routeIn = args.In routeOut = args.QuotedOut case bytes.Equal(disc, jupiterExactOutRouteV2): args, err := decodeJupiterV6ExactOutRouteV2Arg(instruction.Data[8:]) if err != nil { return nil, err } exactOut = true inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.QuotedIn, args.Out, args.RoutePlan, false) routeIn = args.QuotedIn routeOut = args.Out case bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2): args, err := decodeJupiterV6SharedAccountsExactOutRouteV2Arg(instruction.Data[8:]) if err != nil { return nil, err } exactOut = true inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.QuotedIn, args.Out, args.RoutePlan, false) routeIn = args.QuotedIn routeOut = args.Out case bytes.Equal(disc, jupiterRoute): args, err := decodeJupiterV6RouteArg(instruction.Data[8:]) if err != nil { return nil, err } sig, handled, err := parseJupiterPumpAmmRoute(tx, instruction, args.In, args.QuotedOut, args.Plan) if err != nil { return nil, err } if handled { return TxSignalBatch{sig}, nil } inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStats(args.In, args.QuotedOut, args.Plan, true) routeIn = args.In routeOut = args.QuotedOut case bytes.Equal(disc, jupiterSharedAccountsExactOutRoute): args, err := decodeJupiterV6SharedAccountsExactOutRouteArg(instruction.Data[8:]) if err != nil { return nil, err } exactOut = true inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStats(args.QuotedIn, args.Out, args.Plan, false) routeIn = args.QuotedIn routeOut = args.Out case bytes.Equal(disc, jupiterSharedAccountsRoute): args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:]) if err != nil { return nil, err } _ = args inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStats(args.In, args.QuotedOut, args.Plan, true) routeIn = args.In routeOut = args.QuotedOut default: return nil, nil } if bytes.Equal(disc, jupiterRoute) { if len(instruction.Accounts) < 13 { return nil, nil } destMint, err := tx.GetAccount(int(instruction.Accounts[5])) if err != nil { return nil, err } if isToken1Mint(destMint) { pumpKey, err := tx.GetAccount(int(instruction.Accounts[9])) if err != nil { return nil, err } if !pumpKey.Equals(pumpProgramID) { return nil, nil } token0Mint, err := tx.GetAccount(int(instruction.Accounts[12])) if err != nil { return nil, err } token0Amount := decimal.Zero if routeIn > 0 { token0Amount = formatTokenAmount(routeIn) } return TxSignalBatch{&TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Token0Address: token0Mint.String(), Token1Address: destMint.String(), Token0Amount: token0Amount, Token1Amount: decimal.Zero, Program: "Pump", Event: "sell", IsToken2022: false, IsMayhemMode: false, ExactSOL: false, Block: tx.Block, Token0AmountUint64: routeIn, Token1AmountUint64: 0, }}, nil } token0Amount := decimal.Zero if routeOut > 0 { token0Amount = formatTokenAmount(routeOut) } return TxSignalBatch{&TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Token0Address: destMint.String(), Token1Address: wsolMint, Token0Amount: token0Amount, Token1Amount: decimal.Zero, Program: "Pump", Event: "buy", IsToken2022: false, IsMayhemMode: false, ExactSOL: false, Block: tx.Block, Token0AmountUint64: routeOut, Token1AmountUint64: 0, }}, 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, instruction.Accounts) if err != nil { return nil, err } if !ok { return nil, nil } token1Mint := solana.WrappedSol token1IsStable := false srcMint, dstMint, ok, err := jupiterV6SourceDestMints(tx, instruction, disc) if err != nil { return nil, err } if isJupiterV6Token1RequiredDisc(disc) { if !ok { return nil, nil } if !isToken1Mint(srcMint) && !isToken1Mint(dstMint) { return nil, nil } } if ok { if srcMint.Equals(solana.WrappedSol) || dstMint.Equals(solana.WrappedSol) { token1Mint = solana.WrappedSol } else if isStableMint(srcMint) { token1Mint = srcMint token1IsStable = true } else if isStableMint(dstMint) { token1Mint = dstMint token1IsStable = true } } 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 { if token1IsStable { token1Amount = formatTokenAmount(token1AmountUint64) } else { token1Amount = formatSolAmount(token1AmountUint64) } } token1Address := wsolMint if token1IsStable { token1Address = token1Mint.String() } return TxSignalBatch{&TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Token0Address: mint.String(), Token1Address: token1Address, Token0Amount: token0Amount, Token1Amount: token1Amount, Program: "Pump", Event: event, IsToken2022: false, IsMayhemMode: false, ExactSOL: exactSol, Block: tx.Block, Token0AmountUint64: token0AmountUint64, Token1AmountUint64: token1AmountUint64, }}, nil } if wrappedAnyC > 1 { logger.Warn("pumpWrapped at inputIdx!=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", wrappedAnyC) } if wrappedAnyC == 1 && routeIn > 0 && routeOut > 0 { mint, ok, err := findPumpFunMint(tx, instruction.Accounts) if err != nil { return nil, err } if !ok { return nil, nil } token1Mint := solana.WrappedSol token1IsStable := false srcMint, dstMint, ok, err := jupiterV6SourceDestMints(tx, instruction, disc) if err != nil { return nil, err } if isJupiterV6Token1RequiredDisc(disc) { if !ok { return nil, nil } if !isToken1Mint(srcMint) && !isToken1Mint(dstMint) { return nil, nil } } if ok { if srcMint.Equals(solana.WrappedSol) || dstMint.Equals(solana.WrappedSol) { token1Mint = solana.WrappedSol } else if isStableMint(srcMint) { token1Mint = srcMint token1IsStable = true } else if isStableMint(dstMint) { token1Mint = dstMint token1IsStable = true } } event := "sell" exactSol := false var ( token0AmountUint64 uint64 token1AmountUint64 uint64 ) if wrappedAny.IsBuy { event = "buy" exactSol = !exactOut token0AmountUint64 = routeOut token1AmountUint64 = routeIn } else { exactSol = exactOut && routeOut > 0 token0AmountUint64 = routeIn token1AmountUint64 = routeOut } token0Amount := decimal.Zero if token0AmountUint64 > 0 { token0Amount = formatTokenAmount(token0AmountUint64) } token1Amount := decimal.Zero if token1AmountUint64 > 0 { if token1IsStable { token1Amount = formatTokenAmount(token1AmountUint64) } else { token1Amount = formatSolAmount(token1AmountUint64) } } token1Address := wsolMint if token1IsStable { token1Address = token1Mint.String() } return TxSignalBatch{&TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Token0Address: mint.String(), Token1Address: token1Address, 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) } if buySwapCnt > 1 { // multiple pumpSwapBuy at inputIdx=0? should not happen logger.Warn("pumpSwapBuy at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", buySwapCnt) } hasSell := inputAmount > 0 hasBuy := buySwap.InAmount > 0 if hasSell && hasBuy { logger.Warn("pumpSwap buy/sell at inputIdx=0: both found", "tx", tx.Signatures[0].String(), "sellCount", planCount, "buyCount", buySwapCnt) return nil, nil } if !hasSell && !hasBuy { return nil, nil } var ( baseMint solana.PublicKey quoteMint solana.PublicKey destMint solana.PublicKey destMintOK bool sourceMintOK bool ) // existing mint extraction logic only valid for route_v2/ exact_out_route_v2. Keep it but guard. if bytes.Equal(disc, jupiterRouteV2) || bytes.Equal(disc, jupiterSharedAccountsRouteV2) || bytes.Equal(disc, jupiterExactOutRouteV2) || bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2) { if len(instruction.Accounts) < 6 { return nil, fmt.Errorf("not enough accounts for jupiter v6 v2 instruction") } sourceMint, err = tx.GetAccount(int(instruction.Accounts[3])) if err != nil { return nil, err } destMint, err = tx.GetAccount(int(instruction.Accounts[4])) if err != nil { return nil, err } destMintOK = true sourceMintOK = true var ( srcIdx uint8 ) if len(instruction.Accounts) <= 9 { return nil, nil } accounts := instruction.Accounts[8:] for i, acctIdx := range accounts { key, err := tx.GetAccount(int(acctIdx)) if err != nil { return nil, err } if key.Equals(pumpAmmProgramID) { srcIdx = uint8(i + 4) break } } if srcIdx == 0 || srcIdx+1 >= uint8(len(accounts)) { return nil, nil } baseMint, err = tx.GetAccount(int(accounts[srcIdx])) if err != nil { return nil, err } quoteMint, err = tx.GetAccount(int(accounts[srcIdx+1])) if err != nil { return nil, err } if !quoteMint.Equals(solana.WrappedSol) { return nil, nil } } else if bytes.Equal(disc, jupiterSharedAccountsRoute) || bytes.Equal(disc, jupiterSharedAccountsExactOutRoute) { if len(instruction.Accounts) < 12 { return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterSharedAccountsRoute instruction") } sourceMint, err = tx.GetAccount(int(instruction.Accounts[7])) if err != nil { return nil, err } destMint, err = tx.GetAccount(int(instruction.Accounts[8])) if err != nil { return nil, err } destMintOK = true sourceMintOK = true var ( srcIdx uint8 ) if len(instruction.Accounts) <= 12 { return nil, nil } accounts := instruction.Accounts[11:] for i, acctIdx := range accounts { key, err := tx.GetAccount(int(acctIdx)) if err != nil { return nil, err } if key.Equals(pumpAmmProgramID) { srcIdx = uint8(i + 4) break } } if srcIdx == 0 || srcIdx+1 >= uint8(len(accounts)) { return nil, nil } baseMint, err = tx.GetAccount(int(accounts[srcIdx])) if err != nil { return nil, err } quoteMint, err = tx.GetAccount(int(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") } var ( srcIdx uint8 ) accounts := instruction.Accounts[9:] for i, acctIdx := range accounts { key, err := tx.GetAccount(int(acctIdx)) if err != nil { return nil, err } if key.Equals(pumpAmmProgramID) { srcIdx = uint8(i + 4) break } } if srcIdx == 0 || srcIdx+1 >= uint8(len(accounts)) { return nil, nil } baseMint, err = tx.GetAccount(int(accounts[srcIdx])) if err != nil { return nil, err } quoteMint, err = tx.GetAccount(int(accounts[srcIdx+1])) if err != nil { return nil, err } if !quoteMint.Equals(solana.WrappedSol) { return nil, nil } sourceMint = baseMint } if hasSell { if sourceMintOK && !sourceMint.Equals(baseMint) { return nil, nil } } else { if !sourceMintOK { return nil, nil } if !sourceMint.Equals(solana.WrappedSol) && !sourceMint.Equals(solana.SystemProgramID) { return nil, nil } if destMintOK && !destMint.Equals(baseMint) { return nil, nil } } event := "sell" exactSol := false token0AmountUint64 := inputAmount token1AmountUint64 := uint64(0) if hasBuy { event = "buy" exactSol = !exactOut token0AmountUint64 = buySwap.OutAmount token1AmountUint64 = buySwap.InAmount } token0Amount := decimal.Zero if token0AmountUint64 > 0 { token0Amount = formatTokenAmount(token0AmountUint64) } token1Amount := decimal.Zero if token1AmountUint64 > 0 { token1Amount = formatSolAmount(token1AmountUint64) } signal := &TxSignal{ TxHash: tx.Signatures[0].String(), Maker: tx.StaticAccountKeys[0].String(), Token0Address: baseMint.String(), Token1Address: wsolMint, Token0Amount: token0Amount, Token1Amount: token1Amount, Program: "PumpAMM", Event: event, IsToken2022: false, IsMayhemMode: false, ExactSOL: exactSol, Block: tx.Block, Token0AmountUint64: token0AmountUint64, Token1AmountUint64: token1AmountUint64, } return TxSignalBatch{signal}, nil } // keep lints happy if solana-go isn't referenced elsewhere in this file for build tags var _ = solana.PublicKey{}