This commit is contained in:
thloyi
2026-04-28 18:26:43 +08:00
parent cddfcc3eef
commit 7b2a9af978
2 changed files with 107 additions and 41 deletions

View File

@@ -1,21 +1,36 @@
package shreder
import (
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
"github.com/shopspring/decimal"
)
var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
var (
flasBuyTokensIX = []byte{0x00, 0x1, 0x1b}
flasSellTokensIX = []byte{0x01, 0x1, 0x1a}
flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2}
flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2}
flasBonkBuyTokensIX = []byte{0x00, 0x2, 0x7}
flasBonkSellTokensIX = []byte{0x01, 0x2, 0x7}
flasBuyTokensIXs = [][]byte{
{0x00, 0x01, 0x21},
{0x00, 0x01, 0x1b},
}
flasSellTokensIXs = [][]byte{
{0x01, 0x01, 0x1a},
}
flasAmmBuyTokensIXs = [][]byte{
{0x00, 0x02, 0x1f},
{0x00, 0x02, 0x02},
}
flasAmmSellTokensIXs = [][]byte{
{0x01, 0x02, 0x1f},
{0x01, 0x02, 0x02},
}
flasBonkBuyTokensIXs = [][]byte{
{0x00, 0x02, 0x07},
}
flasBonkSellTokensIXs = [][]byte{
{0x01, 0x02, 0x07},
}
)
type flasArgs struct {
@@ -24,6 +39,26 @@ type flasArgs struct {
Placeholder [3]uint8
}
func decodeFlasArgs(data []byte) (flasArgs, error) {
if len(data) < 20 {
return flasArgs{}, fmt.Errorf("data too short for args flas instruction, len: %d", len(data))
}
return flasArgs{
Amount1: binary.LittleEndian.Uint64(data[1:9]),
Amount2: binary.LittleEndian.Uint64(data[9:17]),
Placeholder: [3]uint8{data[17], data[18], data[19]},
}, nil
}
func matchFlasMethod(data []byte, methods [][]byte) bool {
for _, method := range methods {
if matchMethod(data, method) {
return true
}
}
return false
}
func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
@@ -40,24 +75,21 @@ func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data))
}
methodData := instruction.Data[17:20]
//if !matchMethod(methodData, flasBuyTokensIX) {
// return nil, nil
//}
var (
err error
txSignal *TxSignal
)
if matchMethod(methodData, flasBuyTokensIX) {
if matchFlasMethod(methodData, flasBuyTokensIXs) {
txSignal, err = parseFlasBuy(tx, instructionIndex)
} else if matchMethod(methodData, flasSellTokensIX) {
} else if matchFlasMethod(methodData, flasSellTokensIXs) {
txSignal, err = parseFlasSell(tx, instructionIndex)
} else if matchMethod(methodData, flasAmmBuyTokensIX) {
} else if matchFlasMethod(methodData, flasAmmBuyTokensIXs) {
txSignal, err = parseFlasAmmBuy(tx, instructionIndex)
} else if matchMethod(methodData, flasAmmSellTokensIX) {
} else if matchFlasMethod(methodData, flasAmmSellTokensIXs) {
txSignal, err = parseFlasAmmSell(tx, instructionIndex)
} else if matchMethod(methodData, flasBonkBuyTokensIX) {
} else if matchFlasMethod(methodData, flasBonkBuyTokensIXs) {
txSignal, err = parseFlasBonkBuy(tx, instructionIndex)
} else if matchMethod(methodData, flasBonkSellTokensIX) {
} else if matchFlasMethod(methodData, flasBonkSellTokensIXs) {
txSignal, err = parseFlasBonkSell(tx, instructionIndex)
}
if txSignal != nil {
@@ -81,10 +113,16 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal,
return nil, err
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
args, err := decodeFlasArgs(instruction.Data)
if err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
token0Amount := formatTokenAmount(args.Amount1)
token0AmountUint64 := args.Amount1
if len(instruction.Accounts) == 52 {
token0Amount = decimal.Zero
token0AmountUint64 = 0
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
@@ -92,7 +130,7 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal,
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.Amount1),
Token0Amount: token0Amount,
Token1Amount: formatSolAmount(args.Amount2),
Program: "PumpAMM",
Event: "sell",
@@ -100,7 +138,7 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal,
IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block,
Token0AmountUint64: args.Amount1,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: args.Amount2,
}, nil
}
@@ -120,8 +158,8 @@ func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal,
return nil, err
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
args, err := decodeFlasArgs(instruction.Data)
if err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
@@ -159,8 +197,8 @@ func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, er
return nil, err
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
args, err := decodeFlasArgs(instruction.Data)
if err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
@@ -196,11 +234,8 @@ func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, err
if err != nil {
return nil, err
}
if len(instruction.Data) > 20 {
instruction.Data = instruction.Data[:20]
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
args, err := decodeFlasArgs(instruction.Data)
if err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
@@ -225,7 +260,7 @@ func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, err
func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 16 {
if len(instruction.Accounts) < 17 {
return nil, fmt.Errorf("accounts too short")
}
@@ -247,11 +282,8 @@ func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal,
return nil, nil
}
if len(instruction.Data) > 20 {
instruction.Data = instruction.Data[:20]
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
args, err := decodeFlasArgs(instruction.Data)
if err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
@@ -274,7 +306,7 @@ func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal,
func parseFlasBonkSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 16 {
if len(instruction.Accounts) < 17 {
return nil, fmt.Errorf("accounts too short")
}
@@ -296,8 +328,8 @@ func parseFlasBonkSell(tx VersionedTransaction, instructionIndex int) (*TxSignal
return nil, nil
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
args, err := decodeFlasArgs(instruction.Data)
if err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/near/borsh-go"
)
func TestDecodeAxiomArgs(t *testing.T) {
@@ -49,8 +48,8 @@ func TestDecodeAxiomArgs(t *testing.T) {
t.Fatalf("failed to decode hex string: %v", err)
return
}
var args flasArgs
if err := borsh.Deserialize(&args, data[1:]); err != nil {
args, err := decodeFlasArgs(data)
if err != nil {
t.Fatalf("failed to decode Axiom args: %v", err)
return
}
@@ -59,6 +58,41 @@ func TestDecodeAxiomArgs(t *testing.T) {
}
}
func TestDecodeAxiomUpdatedRouteMarkers(t *testing.T) {
tests := []struct {
name string
marker []byte
match func([]byte) bool
}{
{name: "pump buy 0021", marker: []byte{0x00, 0x01, 0x21}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBuyTokensIXs) }},
{name: "pump buy 001b", marker: []byte{0x00, 0x01, 0x1b}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBuyTokensIXs) }},
{name: "pump sell", marker: []byte{0x01, 0x01, 0x1a}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasSellTokensIXs) }},
{name: "pump amm buy", marker: []byte{0x00, 0x02, 0x1f}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasAmmBuyTokensIXs) }},
{name: "pump amm sell", marker: []byte{0x01, 0x02, 0x1f}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasAmmSellTokensIXs) }},
{name: "legacy bonk buy", marker: []byte{0x00, 0x02, 0x07}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBonkBuyTokensIXs) }},
{name: "legacy bonk sell", marker: []byte{0x01, 0x02, 0x07}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBonkSellTokensIXs) }},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data := make([]byte, 20)
data[0] = 0
copy(data[17:20], tt.marker)
args, err := decodeFlasArgs(data)
if err != nil {
t.Fatalf("failed to decode args: %v", err)
}
expected := [3]uint8{tt.marker[0], tt.marker[1], tt.marker[2]}
if args.Placeholder != expected {
t.Fatalf("expected marker %x, got %x", tt.marker, args.Placeholder)
}
if !tt.match(tt.marker) {
t.Fatalf("marker %x did not match route", tt.marker)
}
})
}
}
func toUpdata(slot uint64, tx *solana.Transaction) *SubscribeUpdateTransaction {
signatures := make([][]byte, len(tx.Signatures))
for i, sig := range tx.Signatures {