Add jupiter pumpamm buy pase

This commit is contained in:
bijianing97
2026-01-26 17:18:29 +08:00
parent 741d333e1b
commit 5f97972194

View File

@@ -823,14 +823,37 @@ func decodeJupiterV6SharedAccountsRouteV2Arg(data []byte) (*JupiterV6SharedAccou
return &JupiterV6SharedAccountsRouteV2Arg{ID: id, In: inAmt, QuotedOut: quotedOut, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil
}
func isInputIdx0(idx uint8) bool {
return idx == 0
}
func isPumpSwapSellKind(kind SwapKind) bool {
switch kind {
case PumpSwapSell, PumpSwapSellV2, PumpSwapSellV3:
return true
default:
return false
}
}
func isPumpSwapBuyKind(kind SwapKind) bool {
switch kind {
case PumpSwapBuy, PumpSwapBuyV2, PumpSwapBuyV3:
return true
default:
return false
}
}
func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) (uint64, int) {
var (
ret uint64
i int
)
for _, step := range plan {
if step.InputIdx == 0 &&
(step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) {
if !isInputIdx0(step.InputIdx) || !isPumpSwapSellKind(step.Swap.Kind) {
continue
}
i++
if ret > 0 {
// multiple pumpSwapSell at inputIdx=0? should not happen
@@ -838,7 +861,6 @@ func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) (uint64, int) {
}
ret += amount * uint64(step.Percent) / 100
}
}
return ret, i
}
@@ -848,20 +870,66 @@ func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) {
i int
)
for _, step := range plan {
if step.InputIdx == 0 &&
(step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) {
if !isInputIdx0(step.InputIdx) || !isPumpSwapSellKind(step.Swap.Kind) {
continue
}
i++
if ret > 0 {
// multiple pumpSwapSell at inputIdx=0? should not happen
return 0, i
}
ret += amount * uint64(step.Bps) / 10000
}
}
return ret, i
}
type pumpSwapBuyMatch struct {
InAmount uint64
OutAmount uint64
}
func pumpSwapBuyAtIdx0(in uint64, out uint64, plan []RoutePlanStep) (pumpSwapBuyMatch, int) {
var (
ret pumpSwapBuyMatch
count int
)
for _, step := range plan {
if !isInputIdx0(step.InputIdx) || !isPumpSwapBuyKind(step.Swap.Kind) {
continue
}
count++
if count > 1 {
return pumpSwapBuyMatch{}, count
}
ret.InAmount = in * uint64(step.Percent) / 100
if step.Percent == 100 {
ret.OutAmount = out
}
}
return ret, count
}
func pumpSwapBuyAtIdx0V2(in uint64, out uint64, plan []RoutePlanStepV2) (pumpSwapBuyMatch, int) {
var (
ret pumpSwapBuyMatch
count int
)
for _, step := range plan {
if !isInputIdx0(step.InputIdx) || !isPumpSwapBuyKind(step.Swap.Kind) {
continue
}
count++
if count > 1 {
return pumpSwapBuyMatch{}, count
}
ret.InAmount = in * uint64(step.Bps) / 10000
if step.Bps == 10000 {
ret.OutAmount = out
}
}
return ret, count
}
type pumpWrappedMatch struct {
IsBuy bool
InAmount uint64
@@ -886,6 +954,10 @@ func isPumpWrappedSell(kind SwapKind) bool {
}
}
func isPumpWrappedKind(kind SwapKind) bool {
return isPumpWrappedBuy(kind) || isPumpWrappedSell(kind)
}
func isStableMint(mint solana.PublicKey) bool {
if mint.Equals(usdcMint) {
return true
@@ -918,10 +990,10 @@ func pumpWrappedAtIdx0(in uint64, out uint64, plan []RoutePlanStep) (pumpWrapped
count int
)
for _, step := range plan {
if step.InputIdx != 0 {
if !isInputIdx0(step.InputIdx) {
continue
}
if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) {
if !isPumpWrappedKind(step.Swap.Kind) {
continue
}
count++
@@ -943,10 +1015,10 @@ func pumpWrappedAtIdx0V2(in uint64, out uint64, plan []RoutePlanStepV2) (pumpWra
count int
)
for _, step := range plan {
if step.InputIdx != 0 {
if !isInputIdx0(step.InputIdx) {
continue
}
if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) {
if !isPumpWrappedKind(step.Swap.Kind) {
continue
}
count++
@@ -968,7 +1040,7 @@ func pumpWrappedAny(plan []RoutePlanStep) (pumpWrappedMatch, int) {
count int
)
for _, step := range plan {
if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) {
if !isPumpWrappedKind(step.Swap.Kind) {
continue
}
count++
@@ -986,7 +1058,7 @@ func pumpWrappedAnyV2(plan []RoutePlanStepV2) (pumpWrappedMatch, int) {
count int
)
for _, step := range plan {
if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) {
if !isPumpWrappedKind(step.Swap.Kind) {
continue
}
count++
@@ -998,6 +1070,124 @@ func pumpWrappedAnyV2(plan []RoutePlanStepV2) (pumpWrappedMatch, int) {
return ret, count
}
func pumpRoutePlanStats(in uint64, out uint64, plan []RoutePlanStep, includeInput bool) (uint64, int, pumpSwapBuyMatch, int, pumpWrappedMatch, int, pumpWrappedMatch, int) {
var (
inputAmount uint64
planCount int
)
if includeInput {
inputAmount, planCount = pumpSwapSellAtIdx0(in, plan)
}
buySwap, buySwapCnt := pumpSwapBuyAtIdx0(in, out, plan)
wrapped, wrappedCnt := pumpWrappedAtIdx0(in, out, plan)
wrappedAny, wrappedAnyC := pumpWrappedAny(plan)
return inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC
}
func pumpRoutePlanStatsV2(in uint64, out uint64, plan []RoutePlanStepV2, includeInput bool) (uint64, int, pumpSwapBuyMatch, int, pumpWrappedMatch, int, pumpWrappedMatch, int) {
var (
inputAmount uint64
planCount int
)
if includeInput {
inputAmount, planCount = pumpSwapSellAtIdx0V2(in, plan)
}
buySwap, buySwapCnt := pumpSwapBuyAtIdx0V2(in, out, plan)
wrapped, wrappedCnt := pumpWrappedAtIdx0V2(in, out, plan)
wrappedAny, wrappedAnyC := pumpWrappedAnyV2(plan)
return inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC
}
func parseJupiterPumpAmmRoute(tx *versionedTransaction, instruction compiledInstruction, in uint64, out uint64, plan []RoutePlanStep) (*TxSignal, bool, error) {
var (
isBuy bool
isSell bool
count int
)
for _, step := range plan {
if !isInputIdx0(step.InputIdx) {
continue
}
if isPumpSwapSellKind(step.Swap.Kind) {
isSell = true
count++
} else if isPumpSwapBuyKind(step.Swap.Kind) {
isBuy = true
count++
}
}
if count == 0 {
return nil, false, nil
}
if count > 1 || (isBuy && isSell) {
logger.Warn("pumpamm route at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", count)
return nil, true, nil
}
if len(instruction.Accounts) < 14 {
return nil, true, nil
}
token0Key, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[13]))
if err != nil {
return nil, true, err
}
if isSell {
token0Amount := decimal.Zero
if in > 0 {
token0Amount = formatTokenAmount(in)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Token0Address: token0Key.String(),
Token1Address: wsolMint,
Token0Amount: token0Amount,
Token1Amount: decimal.Zero,
Program: "PumpAMM",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block,
Token0AmountUint64: in,
Token1AmountUint64: 0,
}, true, nil
}
if len(instruction.Accounts) < 15 {
return nil, true, nil
}
wsolKey, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[14]))
if err != nil {
return nil, true, err
}
if !wsolKey.Equals(solana.WrappedSol) {
return nil, true, nil
}
token0Amount := decimal.Zero
if out > 0 {
token0Amount = formatTokenAmount(out)
}
token1Amount := decimal.Zero
if in > 0 {
token1Amount = formatSolAmount(in)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Token0Address: token0Key.String(),
Token1Address: wsolMint,
Token0Amount: token0Amount,
Token1Amount: token1Amount,
Program: "PumpAMM",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: out,
Token1AmountUint64: in,
}, true, nil
}
func findPumpFunMint(staticKeys []solana.PublicKey, accounts []uint8) (solana.PublicKey, bool, error) {
for i, acctIdx := range accounts {
key, err := getStaticKey(staticKeys, int(acctIdx))
@@ -1079,6 +1269,8 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
routeIn uint64
routeOut uint64
planCount int
buySwap pumpSwapBuyMatch
buySwapCnt int
wrapped pumpWrappedMatch
wrappedCnt int
wrappedAny pumpWrappedMatch
@@ -1095,9 +1287,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
if err != nil {
return nil, err
}
inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan)
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.In, args.Out, args.Plan)
wrappedAny, wrappedAnyC = pumpWrappedAnyV2(args.Plan)
inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.In, args.Out, args.Plan, true)
routeIn = args.In
routeOut = args.Out
case bytes.Equal(disc, jupiterSharedAccountsRouteV2):
@@ -1105,9 +1295,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
if err != nil {
return nil, err
}
inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan)
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.In, args.QuotedOut, args.RoutePlan)
wrappedAny, wrappedAnyC = pumpWrappedAnyV2(args.RoutePlan)
inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.In, args.QuotedOut, args.RoutePlan, true)
routeIn = args.In
routeOut = args.QuotedOut
case bytes.Equal(disc, jupiterExactOutRouteV2):
@@ -1116,8 +1304,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, err
}
exactOut = true
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.QuotedIn, args.Out, args.RoutePlan)
wrappedAny, wrappedAnyC = pumpWrappedAnyV2(args.RoutePlan)
inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.QuotedIn, args.Out, args.RoutePlan, false)
routeIn = args.QuotedIn
routeOut = args.Out
case bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2):
@@ -1126,8 +1313,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, err
}
exactOut = true
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.QuotedIn, args.Out, args.RoutePlan)
wrappedAny, wrappedAnyC = pumpWrappedAnyV2(args.RoutePlan)
inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStatsV2(args.QuotedIn, args.Out, args.RoutePlan, false)
routeIn = args.QuotedIn
routeOut = args.Out
case bytes.Equal(disc, jupiterRoute):
@@ -1135,10 +1321,14 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
if err != nil {
return nil, err
}
_ = args
inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan)
wrapped, wrappedCnt = pumpWrappedAtIdx0(args.In, args.QuotedOut, args.Plan)
wrappedAny, wrappedAnyC = pumpWrappedAny(args.Plan)
sig, handled, err := parseJupiterPumpAmmRoute(tx, instruction, args.In, args.QuotedOut, args.Plan)
if err != nil {
return nil, err
}
if handled {
return sig, nil
}
inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStats(args.In, args.QuotedOut, args.Plan, true)
routeIn = args.In
routeOut = args.QuotedOut
case bytes.Equal(disc, jupiterSharedAccountsExactOutRoute):
@@ -1147,8 +1337,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, err
}
exactOut = true
wrapped, wrappedCnt = pumpWrappedAtIdx0(args.QuotedIn, args.Out, args.Plan)
wrappedAny, wrappedAnyC = pumpWrappedAny(args.Plan)
inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStats(args.QuotedIn, args.Out, args.Plan, false)
routeIn = args.QuotedIn
routeOut = args.Out
case bytes.Equal(disc, jupiterSharedAccountsRoute):
@@ -1157,9 +1346,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, err
}
_ = args
inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan)
wrapped, wrappedCnt = pumpWrappedAtIdx0(args.In, args.QuotedOut, args.Plan)
wrappedAny, wrappedAnyC = pumpWrappedAny(args.Plan)
inputAmount, planCount, buySwap, buySwapCnt, wrapped, wrappedCnt, wrappedAny, wrappedAnyC = pumpRoutePlanStats(args.In, args.QuotedOut, args.Plan, true)
routeIn = args.In
routeOut = args.QuotedOut
default:
@@ -1401,12 +1588,33 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
// multiple pumpSwapSell at inputIdx=0? should not happen
logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", planCount)
}
if inputAmount == 0 {
if buySwapCnt > 1 {
// multiple pumpSwapBuy at inputIdx=0? should not happen
logger.Warn("pumpSwapBuy at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", buySwapCnt)
}
hasSell := inputAmount > 0
hasBuy := buySwap.InAmount > 0
if hasSell && hasBuy {
logger.Warn("pumpSwap buy/sell at inputIdx=0: both found", "tx", tx.Signatures[0].String(), "sellCount", planCount, "buyCount", buySwapCnt)
return nil, nil
}
if !hasSell && !hasBuy {
return nil, nil
}
var (
baseMint solana.PublicKey
quoteMint solana.PublicKey
destMint solana.PublicKey
destMintOK bool
sourceMintOK bool
)
// existing mint extraction logic only valid for route_v2/ exact_out_route_v2. Keep it but guard.
if bytes.Equal(disc, jupiterRouteV2) || bytes.Equal(disc, jupiterSharedAccountsRouteV2) {
if bytes.Equal(disc, jupiterRouteV2) ||
bytes.Equal(disc, jupiterSharedAccountsRouteV2) ||
bytes.Equal(disc, jupiterExactOutRouteV2) ||
bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2) {
if len(instruction.Accounts) < 6 {
return nil, fmt.Errorf("not enough accounts for jupiter v6 v2 instruction")
}
@@ -1414,6 +1622,12 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
if err != nil {
return nil, err
}
destMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[4]))
if err != nil {
return nil, err
}
destMintOK = true
sourceMintOK = true
var (
srcIdx uint8
@@ -1436,14 +1650,11 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, nil
}
baseMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx]))
baseMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx]))
if err != nil {
return nil, err
}
if !sourceMint.Equals(baseMint) {
return nil, nil
}
quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1]))
quoteMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1]))
if err != nil {
return nil, err
}
@@ -1451,7 +1662,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, nil
}
} else if bytes.Equal(disc, jupiterSharedAccountsRoute) {
} else if bytes.Equal(disc, jupiterSharedAccountsRoute) || bytes.Equal(disc, jupiterSharedAccountsExactOutRoute) {
if len(instruction.Accounts) < 12 {
return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterSharedAccountsRoute instruction")
}
@@ -1459,6 +1670,12 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
if err != nil {
return nil, err
}
destMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[8]))
if err != nil {
return nil, err
}
destMintOK = true
sourceMintOK = true
var (
srcIdx uint8
)
@@ -1480,15 +1697,12 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, nil
}
baseMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx]))
baseMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx]))
if err != nil {
return nil, err
}
if !sourceMint.Equals(baseMint) {
return nil, nil
}
quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1]))
quoteMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1]))
if err != nil {
return nil, err
}
@@ -1517,35 +1731,72 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
if srcIdx == 0 || srcIdx+1 >= uint8(len(accounts)) {
return nil, nil
}
sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx]))
baseMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx]))
if err != nil {
return nil, err
}
quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1]))
quoteMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1]))
if err != nil {
return nil, err
}
if !quoteMint.Equals(solana.WrappedSol) {
return nil, nil
}
sourceMint = baseMint
}
if hasSell {
if sourceMintOK && !sourceMint.Equals(baseMint) {
return nil, nil
}
} else {
if !sourceMintOK {
return nil, nil
}
if !sourceMint.Equals(solana.WrappedSol) && !sourceMint.Equals(solana.SystemProgramID) {
return nil, nil
}
if destMintOK && !destMint.Equals(baseMint) {
return nil, nil
}
}
event := "sell"
exactSol := false
token0AmountUint64 := inputAmount
token1AmountUint64 := uint64(0)
if hasBuy {
event = "buy"
exactSol = !exactOut
token0AmountUint64 = buySwap.OutAmount
token1AmountUint64 = buySwap.InAmount
}
token0Amount := decimal.Zero
if token0AmountUint64 > 0 {
token0Amount = formatTokenAmount(token0AmountUint64)
}
token1Amount := decimal.Zero
if token1AmountUint64 > 0 {
token1Amount = formatSolAmount(token1AmountUint64)
}
signal := &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Token0Address: sourceMint.String(),
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(inputAmount),
Token1Amount: decimal.Zero,
Token0Amount: token0Amount,
Token1Amount: token1Amount,
Program: "PumpAMM",
Event: "sell",
Event: event,
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
ExactSOL: exactSol,
Block: tx.Block,
Token0AmountUint64: inputAmount,
Token1AmountUint64: 0,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64,
}
return signal, nil