Add JupiterAggregatorV6 pumpfun parse
This commit is contained in:
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user