Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c732bb2b46 |
62
cmd/debug_jupv6/main.go
Normal file
62
cmd/debug_jupv6/main.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
hexData := "bb64facc31c4af14be34e6edcc0000006f03a4df67000000b903320000000300000064342100024b00000000dc0500026310270203"
|
||||
b, err := hex.DecodeString(hexData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
payload := b[8:]
|
||||
|
||||
off := 0
|
||||
read := func(n int) []byte {
|
||||
if off+n > len(payload) {
|
||||
fmt.Printf("OOB read: off=%d n=%d len=%d\n", off, n, len(payload))
|
||||
os.Exit(1)
|
||||
}
|
||||
out := payload[off : off+n]
|
||||
off += n
|
||||
return out
|
||||
}
|
||||
u8 := func() uint8 { return read(1)[0] }
|
||||
leU16 := func() uint16 {
|
||||
b := read(2)
|
||||
return uint16(b[0]) | uint16(b[1])<<8
|
||||
}
|
||||
leU32 := func() uint32 {
|
||||
b := read(4)
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
}
|
||||
leU64 := func() uint64 {
|
||||
b := read(8)
|
||||
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
||||
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
||||
}
|
||||
|
||||
fmt.Printf("payload len=%d\n", len(payload))
|
||||
amountIn := leU64()
|
||||
quotedOut := leU64()
|
||||
slippage := leU16()
|
||||
platform := leU16()
|
||||
posSlip := leU16()
|
||||
fmt.Printf("in=%d out=%d slip=%d plat=%d pos=%d\n", amountIn, quotedOut, slippage, platform, posSlip)
|
||||
|
||||
planLen := leU32()
|
||||
fmt.Printf("planLen=%d\n", planLen)
|
||||
for i := uint32(0); i < planLen; i++ {
|
||||
swapTag := u8()
|
||||
fmt.Printf("step[%d] swapTag=%d (0x%02x) off=%d\n", i, swapTag, swapTag, off)
|
||||
// payload depends on swapTag; we don't know, so just print next few bytes and stop
|
||||
bps := leU16()
|
||||
inIdx := u8()
|
||||
outIdx := u8()
|
||||
fmt.Printf(" bps=%d inIdx=%d outIdx=%d off=%d\n", bps, inIdx, outIdx, off)
|
||||
}
|
||||
fmt.Printf("done off=%d\n", off)
|
||||
}
|
||||
@@ -37,6 +37,7 @@ func main() {
|
||||
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
|
||||
"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority
|
||||
"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config
|
||||
"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program
|
||||
},
|
||||
},
|
||||
"photon": {
|
||||
@@ -44,6 +45,11 @@ func main() {
|
||||
"BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW",
|
||||
},
|
||||
},
|
||||
"jupiterV6": {
|
||||
AccountRequired: []string{
|
||||
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
|
||||
},
|
||||
},
|
||||
// TODO: axiom, gmgn, etc.
|
||||
})
|
||||
if err != nil {
|
||||
@@ -77,7 +83,12 @@ func main() {
|
||||
return
|
||||
case txBatch := <-txCh:
|
||||
//jsonData, _ := json.MarshalIndent(txBatch, "", " ")
|
||||
fmt.Println(txBatch[0].TxHash)
|
||||
for _, tx := range txBatch {
|
||||
if tx.Label == "jupiterV6" {
|
||||
fmt.Println("===============", tx.TxHash, tx.Token0Address, tx.Token0Amount)
|
||||
}
|
||||
}
|
||||
//fmt.Println(txBatch[0].TxHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type AddressTables struct {
|
||||
}
|
||||
|
||||
func NewAddressTables(rpcClient *rpc.Client) *AddressTables {
|
||||
pool, _ := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
||||
pool, _ := ants.NewPool(5, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
||||
cache, _ := lru.New[solana.PublicKey, []solana.PublicKey](10000)
|
||||
return &AddressTables{
|
||||
rpcClient: rpcClient,
|
||||
|
||||
2570
pkg/shreder/juptierv6-idl.json
Normal file
2570
pkg/shreder/juptierv6-idl.json
Normal file
File diff suppressed because it is too large
Load Diff
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{}
|
||||
88
pkg/shreder/juptierv6_test.go
Normal file
88
pkg/shreder/juptierv6_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeRouteV2Arg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 0",
|
||||
hexData: "bb64facc31c4af14809fd500000000002222e8db1800000064000a000000020000005601fe102700016310270102",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 1",
|
||||
hexData: "bb64facc31c4af144ff91634b90000004e6c4d05000000002c013200000003000000520000000000000000102700014f102701024310270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 2",
|
||||
hexData: "bb64facc31c4af14ba2eafa02c1d0000777a9b2200000000f4010a0000000100000052000000000000000010270001",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 3",
|
||||
hexData: "bb64facc31c4af144a3521186b07000030508d0e00000000c201320000000300000052000000000000000010270001740110270102590010270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 4",
|
||||
hexData: "bb64facc31c4af14092d05050000000013701f198c0100008102380100000300000059011027000168001027010251000000000000000010270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 5",
|
||||
hexData: "bb64facc31c4af1480969800000000006f44ad39bd0000001202320000000200000068001027000151000000000000000010270102",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("raw bytes: %x", instrData[8:])
|
||||
args, err := decodeJupiterV6RouteV2Arg(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode jupiter arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeRouteArg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 0",
|
||||
hexData: "e517cb977ae3ad2a030000004f6400014f64010251000000000000000064020340420f00000000005c1c81900e000000640000",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 1",
|
||||
hexData: "e517cb977ae3ad2a0200000028640001510000000000000000640102c09ee605000000005e1bc48efa000000d00700",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("raw bytes: %x", instrData[8:])
|
||||
args, err := decodeJupiterV6RouteArg(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode jupiter arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
1
pkg/shreder/okxonchainlab.go
Normal file
1
pkg/shreder/okxonchainlab.go
Normal file
@@ -0,0 +1 @@
|
||||
package shreder
|
||||
@@ -29,6 +29,7 @@ func SetLogLevel(level slog.Level) {
|
||||
|
||||
type TxSignal struct {
|
||||
Source string `json:"source"`
|
||||
Label string `json:"label"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
Maker string `json:"maker"`
|
||||
Token0Address string `json:"token0_address"`
|
||||
|
||||
@@ -42,8 +42,23 @@ var (
|
||||
flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
|
||||
|
||||
terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
|
||||
|
||||
jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
|
||||
)
|
||||
|
||||
type AccountNotFoundError struct {
|
||||
Index int
|
||||
Len int
|
||||
}
|
||||
|
||||
func NewAccountNotFoundError(i, l int) error {
|
||||
return &AccountNotFoundError{i, l}
|
||||
}
|
||||
|
||||
func (e AccountNotFoundError) Error() string {
|
||||
return fmt.Sprintf("account index %d out of range, len=%d", e.Index, e.Len)
|
||||
}
|
||||
|
||||
// instruction discriminators
|
||||
var (
|
||||
pumpCreateCoinIX = []byte{24, 30, 200, 40, 5, 28, 7, 119}
|
||||
@@ -198,18 +213,30 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables)
|
||||
instructions := versioned.Message.Instructions
|
||||
|
||||
if loader != nil && len(versioned.Message.AddressTableLookups) > 0 {
|
||||
// currently we only care about photon table lookup
|
||||
lookupTableOk := true
|
||||
for _, lookup := range versioned.Message.AddressTableLookups {
|
||||
if len(lookup.WritableIndexes) == 0 {
|
||||
continue
|
||||
}
|
||||
accounts := loader.GetAddressTable(lookup.AccountKey, lookup.WritableIndexes)
|
||||
if len(accounts) != len(lookup.WritableIndexes) {
|
||||
lookupTableOk = false
|
||||
break
|
||||
}
|
||||
staticKeys = append(staticKeys, accounts...)
|
||||
accounts2 := loader.GetAddressTable(lookup.AccountKey, lookup.ReadonlyIndexes)
|
||||
if len(accounts2) != len(lookup.ReadonlyIndexes) {
|
||||
break
|
||||
|
||||
}
|
||||
if lookupTableOk {
|
||||
for _, lookup := range versioned.Message.AddressTableLookups {
|
||||
if len(lookup.ReadonlyIndexes) == 0 {
|
||||
continue
|
||||
}
|
||||
accounts := loader.GetAddressTable(lookup.AccountKey, lookup.ReadonlyIndexes)
|
||||
if len(accounts) != len(lookup.ReadonlyIndexes) {
|
||||
break
|
||||
}
|
||||
staticKeys = append(staticKeys, accounts...)
|
||||
}
|
||||
staticKeys = append(staticKeys, accounts2...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +280,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables)
|
||||
case terminalProgramID:
|
||||
txRes, err := parseTermInstruction(versioned, i)
|
||||
parsed = appendParsed(parsed, txRes, err, txHash, "terminal")
|
||||
//case jupiterV6ProgramID:
|
||||
// txRes, err := parseJupiterV6Instruction(versioned, i)
|
||||
// parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +291,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables)
|
||||
|
||||
func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal {
|
||||
if err != nil {
|
||||
//if errors.Is(err, &AccountNotFoundError{}) {
|
||||
//
|
||||
//}
|
||||
logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:]))
|
||||
return list
|
||||
}
|
||||
@@ -331,11 +364,8 @@ func formatSolAmount(lamports uint64) decimal.Decimal {
|
||||
}
|
||||
|
||||
func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) {
|
||||
if index < 0 {
|
||||
return solana.PublicKey{}, fmt.Errorf("account index %d less then 0", index)
|
||||
}
|
||||
if index >= len(static) {
|
||||
return solana.PublicKey{}, fmt.Errorf("account index %d out of range", index)
|
||||
if index < 0 || index >= len(static) {
|
||||
return solana.PublicKey{}, NewAccountNotFoundError(index, len(static))
|
||||
}
|
||||
return static[index], nil
|
||||
}
|
||||
@@ -381,6 +411,7 @@ func parsePumpCreate(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: creator.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -421,6 +452,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: args.Creator.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -484,6 +516,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -541,6 +574,7 @@ func parsePumpSell(tx *versionedTransaction, instruction *compiledInstruction) (
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: seller.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -599,6 +633,7 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal,
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "azcz",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -642,6 +677,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "azcz",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -696,6 +732,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "f5tf",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -766,6 +803,7 @@ func parseFlasAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -805,6 +843,7 @@ func parseFlasAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal,
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -844,6 +883,7 @@ func parseFlasSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -882,6 +922,7 @@ func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -948,6 +989,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
solAmount := args.SolAmount * (100000000 - 1234568) / 100000000
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "photon",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1003,6 +1045,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
solAmount := args.FromAmount * (100000000 - 1234568) / 100000000
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "photon",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1084,6 +1127,7 @@ func parseTermAmmSell(tx *versionedTransaction, instruction *compiledInstruction
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "term",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1119,6 +1163,7 @@ func parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "term",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1153,6 +1198,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "term",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1224,6 +1270,7 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pumpamm",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1270,6 +1317,7 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pumpamm",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1323,6 +1371,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "bobo",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1383,6 +1432,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e
|
||||
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25])
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "qtkv",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1421,6 +1471,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal
|
||||
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25])
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "qtkv",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1459,6 +1510,7 @@ func parseQtkvBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "qtkv",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1512,6 +1564,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "fjsz",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
|
||||
Reference in New Issue
Block a user