Files
libsam/pkg/shreder/juptierv6.go

1069 lines
28 KiB
Go
Raw Normal View History

2026-01-06 16:42:07 +08:00
package shreder
import (
"bytes"
"encoding/binary"
"fmt"
bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
var (
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}
)
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> }
// 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<RemainingAccountsInfo> 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<CandidateSwap> }
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<CandidateSwap> --------
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<RemainingAccountsInfo> --------
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<RoutePlanStepV2>: 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
}
2026-01-07 11:18:02 +08:00
func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) (uint64, int) {
var (
ret uint64
i int
)
2026-01-06 16:42:07 +08:00
for _, step := range plan {
if step.InputIdx == 0 &&
(step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) {
2026-01-07 11:18:02 +08:00
i++
if ret > 0 {
// multiple pumpSwapSell at inputIdx=0? should not happen
return 0, i
}
2026-01-06 16:42:07 +08:00
ret += amount * uint64(step.Percent) / 100
}
}
2026-01-07 11:18:02 +08:00
return ret, i
2026-01-06 16:42:07 +08:00
}
2026-01-07 11:18:02 +08:00
func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) {
var (
ret uint64
i int
)
2026-01-06 16:42:07 +08:00
for _, step := range plan {
if step.InputIdx == 0 &&
(step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) {
2026-01-07 11:18:02 +08:00
i++
if ret > 0 {
// multiple pumpSwapSell at inputIdx=0? should not happen
return 0, i
}
2026-01-06 16:42:07 +08:00
ret += amount * uint64(step.Bps) / 10000
}
}
2026-01-07 11:18:02 +08:00
return ret, i
2026-01-06 16:42:07 +08:00
}
// only decodes inputIdx = 0 container pumpSwap instructions for now
func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
msg := tx.Message
if instructionIndex >= len(msg.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := msg.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
2026-01-07 11:18:02 +08:00
planCount int
2026-01-06 16:42:07 +08:00
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
}
2026-01-07 11:18:02 +08:00
inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan)
2026-01-06 16:42:07 +08:00
case bytes.Equal(disc, jupiterSharedAccountsRouteV2):
args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:])
if err != nil {
return nil, err
}
2026-01-07 11:18:02 +08:00
inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan)
2026-01-06 16:42:07 +08:00
case bytes.Equal(disc, jupiterRoute):
args, err := decodeJupiterV6RouteArg(instruction.Data[8:])
if err != nil {
return nil, err
}
_ = args
2026-01-07 11:18:02 +08:00
inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan)
2026-01-06 16:42:07 +08:00
case bytes.Equal(disc, jupiterSharedAccountsRoute):
args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:])
if err != nil {
return nil, err
}
_ = args
2026-01-07 11:18:02 +08:00
inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan)
2026-01-06 16:42:07 +08:00
default:
return nil, nil
}
2026-01-07 11:18:02 +08:00
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)
}
2026-01-06 16:42:07 +08:00
if inputAmount == 0 {
return nil, nil
}
// 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) {
if len(instruction.Accounts) < 6 {
return nil, fmt.Errorf("not enough accounts for jupiter v6 v2 instruction")
}
sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
2026-01-07 11:18:02 +08:00
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
}
2026-01-06 16:42:07 +08:00
} else if bytes.Equal(disc, jupiterSharedAccountsRoute) {
if len(instruction.Accounts) < 12 {
return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterSharedAccountsRoute instruction")
}
sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
2026-01-07 11:18:02 +08:00
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
}
2026-01-06 16:42:07 +08:00
} else {
if len(instruction.Accounts) < 10 {
return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterRoute instruction")
}
var (
srcIdx uint8
)
for i, acctIdx := range instruction.Accounts {
2026-01-07 11:18:02 +08:00
if i < 9 {
2026-01-06 16:42:07 +08:00
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
}
2026-01-07 11:18:02 +08:00
quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1]))
2026-01-06 16:42:07 +08:00
if err != nil {
return nil, err
}
2026-01-07 11:18:02 +08:00
if !quoteMint.Equals(solana.WrappedSol) {
2026-01-06 16:42:07 +08:00
return nil, nil
}
}
signal := &TxSignal{
Label: "jupiterV6",
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Token0Address: sourceMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(inputAmount),
Token1Amount: decimal.Zero,
Program: "PumpAMM",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block,
Token0AmountUint64: inputAmount,
Token1AmountUint64: 0,
}
return signal, nil
}
// keep lints happy if solana-go isn't referenced elsewhere in this file for build tags
var _ = solana.PublicKey{}