fix looptable index
This commit is contained in:
983
pkg/shreder/juptierv6.go
Normal file
983
pkg/shreder/juptierv6.go
Normal file
@@ -0,0 +1,983 @@
|
||||
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
|
||||
}
|
||||
|
||||
func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) uint64 {
|
||||
var ret uint64
|
||||
for _, step := range plan {
|
||||
if step.InputIdx == 0 &&
|
||||
(step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) {
|
||||
ret += amount * uint64(step.Percent) / 100
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) uint64 {
|
||||
var ret uint64
|
||||
for _, step := range plan {
|
||||
if step.InputIdx == 0 &&
|
||||
(step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) {
|
||||
ret += amount * uint64(step.Bps) / 10000
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// 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
|
||||
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 = 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)
|
||||
|
||||
case bytes.Equal(disc, jupiterRoute):
|
||||
args, err := decodeJupiterV6RouteArg(instruction.Data[8:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = args
|
||||
inputAmount = 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)
|
||||
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
} 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
|
||||
}
|
||||
} 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 {
|
||||
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
|
||||
}
|
||||
distMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !distMint.Equals(solana.WrappedSol) {
|
||||
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{}
|
||||
Reference in New Issue
Block a user