Add JupiterAggregatorV6 pumpfun parse

This commit is contained in:
bijianing97
2026-01-22 17:10:13 +08:00
parent 36db4729d4
commit 45107aa8c3
3 changed files with 278 additions and 13 deletions

View File

@@ -13,22 +13,25 @@ import (
"github.com/samlior/libsam/pkg/shreder" "github.com/samlior/libsam/pkg/shreder"
) )
// OKX tx const (
// const dlmmSignature = "4W8gD2iEYyvzpPiW9BhdH5hUrfXhqH46ziLzPkaaxmaA8XXK53erUvrPdZ5cY2XrgWwix1hmRajUnGAiNp4cSGpN" rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
txSignature = "4rkbkLCUQxc89Aq1BZxU1w4LDQtnCtoUJ6VNHmVec8Kqngr5T89xAXahubLFg8DF6iFGzJ39N8V8n6LFtARDUJT9"
const dlmmSignature = "3Kcm9rqG9mJ6PCM5DuUoZ6jk3kzbH7J5GP488E5MKMwodf2NXgddygEcWBmzRBrV2YZFmXtG22gvcJixqdsCjRPn" labelFilter = ""
enableStats = true
)
func main() { func main() {
const rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d" if rpcURL == "" || rpcURL == "REPLACE_WITH_RPC_URL" {
if rpcURL == "" { log.Fatal("rpcURL is not set in cmd/dlmmparse/main.go")
log.Fatal("SOL_RPC_URL is not set") }
if txSignature == "" || txSignature == "REPLACE_WITH_TX_SIGNATURE" {
log.Fatal("txSignature is not set in cmd/dlmmparse/main.go")
} }
client := rpc.New(rpcURL) client := rpc.New(rpcURL)
sig, err := solana.SignatureFromBase58(dlmmSignature) sig, err := solana.SignatureFromBase58(txSignature)
if err != nil { if err != nil {
log.Fatalf("invalid dlmmSignature: %v", err) log.Fatalf("invalid txSignature: %v", err)
} }
version := uint64(0) version := uint64(0)
tx, err := client.GetTransaction( tx, err := client.GetTransaction(
@@ -72,7 +75,7 @@ func main() {
} }
update := toSubscribeUpdate(tx.Slot, rawTx) update := toSubscribeUpdate(tx.Slot, rawTx)
signals := shreder.ParseTransaction(update, nil, true) signals := shreder.ParseTransaction(update, nil, enableStats)
if len(signals) == 0 { if len(signals) == 0 {
fmt.Println("no signals parsed") fmt.Println("no signals parsed")
return return
@@ -80,7 +83,10 @@ func main() {
printed := false printed := false
for _, signal := range signals { for _, signal := range signals {
if signal == nil || signal.Label != "dlmm" { if signal == nil {
continue
}
if labelFilter != "" && signal.Label != labelFilter {
continue continue
} }
printed = true printed = true
@@ -95,7 +101,11 @@ func main() {
return return
} }
fmt.Println("no dlmm signal parsed, dump all signals:") if labelFilter != "" {
fmt.Printf("no %s signal parsed, dump all signals:\n", labelFilter)
} else {
fmt.Println("no matching signal parsed, dump all signals:")
}
for _, signal := range signals { for _, signal := range signals {
if signal == nil { if signal == nil {
continue continue

View File

@@ -858,6 +858,101 @@ func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) {
return ret, i return ret, i
} }
type pumpWrappedMatch struct {
IsBuy bool
InAmount uint64
OutAmount uint64
}
func isPumpWrappedBuy(kind SwapKind) bool {
switch kind {
case PumpWrappedBuy, PumpWrappedBuyV2, PumpWrappedBuyV3, PumpWrappedBuyV4:
return true
default:
return false
}
}
func isPumpWrappedSell(kind SwapKind) bool {
switch kind {
case PumpWrappedSell, PumpWrappedSellV2, PumpWrappedSellV3, PumpWrappedSellV4:
return true
default:
return false
}
}
func pumpWrappedAtIdx0(in uint64, out uint64, plan []RoutePlanStep) (pumpWrappedMatch, int) {
var (
ret pumpWrappedMatch
count int
)
for _, step := range plan {
if step.InputIdx != 0 {
continue
}
if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) {
continue
}
count++
if count > 1 {
return pumpWrappedMatch{}, count
}
ret.IsBuy = isPumpWrappedBuy(step.Swap.Kind)
ret.InAmount = in * uint64(step.Percent) / 100
if step.Percent == 100 {
ret.OutAmount = out
}
}
return ret, count
}
func pumpWrappedAtIdx0V2(in uint64, out uint64, plan []RoutePlanStepV2) (pumpWrappedMatch, int) {
var (
ret pumpWrappedMatch
count int
)
for _, step := range plan {
if step.InputIdx != 0 {
continue
}
if !isPumpWrappedBuy(step.Swap.Kind) && !isPumpWrappedSell(step.Swap.Kind) {
continue
}
count++
if count > 1 {
return pumpWrappedMatch{}, count
}
ret.IsBuy = isPumpWrappedBuy(step.Swap.Kind)
ret.InAmount = in * uint64(step.Bps) / 10000
if step.Bps == 10000 {
ret.OutAmount = out
}
}
return ret, count
}
func findPumpFunMint(staticKeys []solana.PublicKey, accounts []uint8) (solana.PublicKey, bool, error) {
for i, acctIdx := range accounts {
key, err := getStaticKey(staticKeys, int(acctIdx))
if err != nil {
return solana.PublicKey{}, false, err
}
if !key.Equals(pumpProgramID) {
continue
}
if i+3 >= len(accounts) {
return solana.PublicKey{}, false, nil
}
mint, err := getStaticKey(staticKeys, int(accounts[i+3]))
if err != nil {
return solana.PublicKey{}, false, err
}
return mint, true, nil
}
return solana.PublicKey{}, false, nil
}
// only decodes inputIdx = 0 container pumpSwap instructions for now // only decodes inputIdx = 0 container pumpSwap instructions for now
func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
msg := tx.Message msg := tx.Message
@@ -879,6 +974,9 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
sourceMint solana.PublicKey sourceMint solana.PublicKey
inputAmount uint64 inputAmount uint64
planCount int planCount int
wrapped pumpWrappedMatch
wrappedCnt int
exactOut bool
err error err error
) )
@@ -891,12 +989,28 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
return nil, err return nil, err
} }
inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan) inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan)
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.In, args.Out, args.Plan)
case bytes.Equal(disc, jupiterSharedAccountsRouteV2): case bytes.Equal(disc, jupiterSharedAccountsRouteV2):
args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:]) args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan) inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan)
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.In, args.QuotedOut, args.RoutePlan)
case bytes.Equal(disc, jupiterExactOutRouteV2):
args, err := decodeJupiterV6ExactOutRouteV2Arg(instruction.Data[8:])
if err != nil {
return nil, err
}
exactOut = true
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.QuotedIn, args.Out, args.RoutePlan)
case bytes.Equal(disc, jupiterSharedAccountsExactOutRouteV2):
args, err := decodeJupiterV6SharedAccountsExactOutRouteV2Arg(instruction.Data[8:])
if err != nil {
return nil, err
}
exactOut = true
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.QuotedIn, args.Out, args.RoutePlan)
case bytes.Equal(disc, jupiterRoute): case bytes.Equal(disc, jupiterRoute):
args, err := decodeJupiterV6RouteArg(instruction.Data[8:]) args, err := decodeJupiterV6RouteArg(instruction.Data[8:])
if err != nil { if err != nil {
@@ -904,6 +1018,14 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
} }
_ = args _ = args
inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan)
wrapped, wrappedCnt = pumpWrappedAtIdx0(args.In, args.QuotedOut, args.Plan)
case bytes.Equal(disc, jupiterSharedAccountsExactOutRoute):
args, err := decodeJupiterV6SharedAccountsExactOutRouteArg(instruction.Data[8:])
if err != nil {
return nil, err
}
exactOut = true
wrapped, wrappedCnt = pumpWrappedAtIdx0(args.QuotedIn, args.Out, args.Plan)
case bytes.Equal(disc, jupiterSharedAccountsRoute): case bytes.Equal(disc, jupiterSharedAccountsRoute):
args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:]) args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:])
if err != nil { if err != nil {
@@ -911,9 +1033,62 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
} }
_ = args _ = args
inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan)
wrapped, wrappedCnt = pumpWrappedAtIdx0(args.In, args.QuotedOut, args.Plan)
default: default:
return nil, nil return nil, nil
} }
if wrappedCnt > 1 {
logger.Warn("pumpWrapped at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", wrappedCnt)
}
if wrapped.InAmount > 0 {
mint, ok, err := findPumpFunMint(tx.Message.StaticAccountKeys, instruction.Accounts)
if err != nil {
return nil, err
}
if !ok {
return nil, nil
}
event := "sell"
exactSol := false
var (
token0AmountUint64 uint64
token1AmountUint64 uint64
)
if wrapped.IsBuy {
event = "buy"
exactSol = !exactOut
token0AmountUint64 = wrapped.OutAmount
token1AmountUint64 = wrapped.InAmount
} else {
exactSol = exactOut && wrapped.OutAmount > 0
token0AmountUint64 = wrapped.InAmount
token1AmountUint64 = wrapped.OutAmount
}
token0Amount := decimal.Zero
if token0AmountUint64 > 0 {
token0Amount = formatTokenAmount(token0AmountUint64)
}
token1Amount := decimal.Zero
if token1AmountUint64 > 0 {
token1Amount = formatSolAmount(token1AmountUint64)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: token0Amount,
Token1Amount: token1Amount,
Program: "Pump",
Event: event,
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: exactSol,
Block: tx.Block,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64,
}, nil
}
if planCount > 1 { if planCount > 1 {
// multiple pumpSwapSell at inputIdx=0? should not happen // multiple pumpSwapSell at inputIdx=0? should not happen
logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", planCount) logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", planCount)

View File

@@ -289,3 +289,83 @@ func TestParsePhotonBuy(t *testing.T) {
t.Fatalf("expected token1 amount 1955555553, got %d", signal.Token1AmountUint64) t.Fatalf("expected token1 amount 1955555553, got %d", signal.Token1AmountUint64)
} }
} }
func TestParseJupiterV6PumpFunBuy(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "4QF5whXwjx234fMXeH3HrJCy5knFJmKPtgbXys8xKGz1pZypqPvXBr4BoAqXfYn8jLL4HXPY1pcvxCCW1XREFNxd"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "jupiterv6" {
t.Fatalf("expected jupiterv6 signal, got %s", signal.Label)
}
if signal.Event != "buy" {
t.Fatalf("expected buy event, got %s", signal.Event)
}
if signal.Maker != "92ySgsZs3rsrUAq2aeEqYacXQQGmz6e4xHPrRGxLDJXb" {
t.Fatalf("expected maker 92ySgsZs3rsrUAq2aeEqYacXQQGmz6e4xHPrRGxLDJXb, got %s", signal.Maker)
}
if signal.Token0Address != "5kSWidFwDKPZiNf52TfincpVn8ufvkAfEzZ9pk8Dpump" {
t.Fatalf("expected token0 address 5kSWidFwDKPZiNf52TfincpVn8ufvkAfEzZ9pk8Dpump, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 2410530637576 {
t.Fatalf("expected token0 amount 2410530637576, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 380000000 {
t.Fatalf("expected token1 amount 380000000, got %d", signal.Token1AmountUint64)
}
if !signal.ExactSOL {
t.Fatalf("expected ExactSOL true, got false")
}
}
func TestParseJupiterV6PumpFunSell(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "yCnE7ZA8dqB5iAZtwpSN2ar5HXh3gBjgaG2xtnwXDPFyHAm5XFU8642uTZTH5A2iPQ6G9hrj5eEPAJiWrfe38gM"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "jupiterv6" {
t.Fatalf("expected jupiterv6 signal, got %s", signal.Label)
}
if signal.Event != "sell" {
t.Fatalf("expected sell event, got %s", signal.Event)
}
if signal.Maker != "CGfWcKKcVQNBCL1vpxXdg6rvfYpQmnS3WkyA22Lk5XnZ" {
t.Fatalf("expected maker CGfWcKKcVQNBCL1vpxXdg6rvfYpQmnS3WkyA22Lk5XnZ, got %s", signal.Maker)
}
if signal.Token0Address != "wp8Mwxy7btAD9hNWsfJyoPNJnjXS9fuNG4mnhQZpump" {
t.Fatalf("expected token0 address wp8Mwxy7btAD9hNWsfJyoPNJnjXS9fuNG4mnhQZpump, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 127531720509990 {
t.Fatalf("expected token0 amount 127531720509990, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 5296451290 {
t.Fatalf("expected token1 amount 5296451290, got %d", signal.Token1AmountUint64)
}
if signal.ExactSOL {
t.Fatalf("expected ExactSOL false, got true")
}
}