Add JupiterAggregatorV6 pumpfun parse
This commit is contained in:
@@ -13,22 +13,25 @@ import (
|
||||
"github.com/samlior/libsam/pkg/shreder"
|
||||
)
|
||||
|
||||
// OKX tx
|
||||
// const dlmmSignature = "4W8gD2iEYyvzpPiW9BhdH5hUrfXhqH46ziLzPkaaxmaA8XXK53erUvrPdZ5cY2XrgWwix1hmRajUnGAiNp4cSGpN"
|
||||
|
||||
const dlmmSignature = "3Kcm9rqG9mJ6PCM5DuUoZ6jk3kzbH7J5GP488E5MKMwodf2NXgddygEcWBmzRBrV2YZFmXtG22gvcJixqdsCjRPn"
|
||||
|
||||
const (
|
||||
rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
|
||||
txSignature = "4rkbkLCUQxc89Aq1BZxU1w4LDQtnCtoUJ6VNHmVec8Kqngr5T89xAXahubLFg8DF6iFGzJ39N8V8n6LFtARDUJT9"
|
||||
labelFilter = ""
|
||||
enableStats = true
|
||||
)
|
||||
|
||||
func main() {
|
||||
const rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
|
||||
if rpcURL == "" {
|
||||
log.Fatal("SOL_RPC_URL is not set")
|
||||
if rpcURL == "" || rpcURL == "REPLACE_WITH_RPC_URL" {
|
||||
log.Fatal("rpcURL is not set in cmd/dlmmparse/main.go")
|
||||
}
|
||||
if txSignature == "" || txSignature == "REPLACE_WITH_TX_SIGNATURE" {
|
||||
log.Fatal("txSignature is not set in cmd/dlmmparse/main.go")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcURL)
|
||||
sig, err := solana.SignatureFromBase58(dlmmSignature)
|
||||
sig, err := solana.SignatureFromBase58(txSignature)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid dlmmSignature: %v", err)
|
||||
log.Fatalf("invalid txSignature: %v", err)
|
||||
}
|
||||
version := uint64(0)
|
||||
tx, err := client.GetTransaction(
|
||||
@@ -72,7 +75,7 @@ func main() {
|
||||
}
|
||||
|
||||
update := toSubscribeUpdate(tx.Slot, rawTx)
|
||||
signals := shreder.ParseTransaction(update, nil, true)
|
||||
signals := shreder.ParseTransaction(update, nil, enableStats)
|
||||
if len(signals) == 0 {
|
||||
fmt.Println("no signals parsed")
|
||||
return
|
||||
@@ -80,7 +83,10 @@ func main() {
|
||||
|
||||
printed := false
|
||||
for _, signal := range signals {
|
||||
if signal == nil || signal.Label != "dlmm" {
|
||||
if signal == nil {
|
||||
continue
|
||||
}
|
||||
if labelFilter != "" && signal.Label != labelFilter {
|
||||
continue
|
||||
}
|
||||
printed = true
|
||||
@@ -95,7 +101,11 @@ func main() {
|
||||
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 {
|
||||
if signal == nil {
|
||||
continue
|
||||
@@ -858,6 +858,101 @@ func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) {
|
||||
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
|
||||
func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
|
||||
msg := tx.Message
|
||||
@@ -879,6 +974,9 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
|
||||
sourceMint solana.PublicKey
|
||||
inputAmount uint64
|
||||
planCount int
|
||||
wrapped pumpWrappedMatch
|
||||
wrappedCnt int
|
||||
exactOut bool
|
||||
err error
|
||||
)
|
||||
|
||||
@@ -891,12 +989,28 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
|
||||
return nil, err
|
||||
}
|
||||
inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan)
|
||||
wrapped, wrappedCnt = pumpWrappedAtIdx0V2(args.In, args.Out, args.Plan)
|
||||
case bytes.Equal(disc, jupiterSharedAccountsRouteV2):
|
||||
args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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):
|
||||
args, err := decodeJupiterV6RouteArg(instruction.Data[8:])
|
||||
if err != nil {
|
||||
@@ -904,6 +1018,14 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
|
||||
}
|
||||
_ = args
|
||||
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):
|
||||
args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:])
|
||||
if err != nil {
|
||||
@@ -911,9 +1033,62 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (
|
||||
}
|
||||
_ = args
|
||||
inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan)
|
||||
wrapped, wrappedCnt = pumpWrappedAtIdx0(args.In, args.QuotedOut, args.Plan)
|
||||
default:
|
||||
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 {
|
||||
// multiple pumpSwapSell at inputIdx=0? should not happen
|
||||
logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", planCount)
|
||||
|
||||
@@ -289,3 +289,83 @@ func TestParsePhotonBuy(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user