2026-01-28 14:11:34 +08:00
|
|
|
package shreder
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-28 18:26:43 +08:00
|
|
|
"encoding/binary"
|
2026-01-28 14:11:34 +08:00
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/gagliardetto/solana-go"
|
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
|
|
|
|
|
var (
|
2026-04-28 18:26:43 +08:00
|
|
|
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},
|
|
|
|
|
}
|
2026-01-28 14:11:34 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type flasArgs struct {
|
|
|
|
|
Amount1 uint64
|
|
|
|
|
Amount2 uint64
|
|
|
|
|
Placeholder [3]uint8
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 18:26:43 +08:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 14:11:34 +08:00
|
|
|
func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
|
|
|
|
|
if instructionIndex >= len(tx.Instructions) {
|
|
|
|
|
return nil, fmt.Errorf("instruction index out of bounds")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instruction := tx.Instructions[instructionIndex]
|
|
|
|
|
if len(instruction.Data) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("data is empty")
|
|
|
|
|
}
|
|
|
|
|
if len(instruction.Data) == 10 && instruction.Data[0] == 1 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
if len(instruction.Data) < 20 {
|
|
|
|
|
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data))
|
|
|
|
|
}
|
|
|
|
|
methodData := instruction.Data[17:20]
|
|
|
|
|
var (
|
|
|
|
|
err error
|
|
|
|
|
txSignal *TxSignal
|
|
|
|
|
)
|
2026-04-28 18:26:43 +08:00
|
|
|
if matchFlasMethod(methodData, flasBuyTokensIXs) {
|
2026-01-28 14:11:34 +08:00
|
|
|
txSignal, err = parseFlasBuy(tx, instructionIndex)
|
2026-04-28 18:26:43 +08:00
|
|
|
} else if matchFlasMethod(methodData, flasSellTokensIXs) {
|
2026-01-28 14:11:34 +08:00
|
|
|
txSignal, err = parseFlasSell(tx, instructionIndex)
|
2026-04-28 18:26:43 +08:00
|
|
|
} else if matchFlasMethod(methodData, flasAmmBuyTokensIXs) {
|
2026-01-28 14:11:34 +08:00
|
|
|
txSignal, err = parseFlasAmmBuy(tx, instructionIndex)
|
2026-04-28 18:26:43 +08:00
|
|
|
} else if matchFlasMethod(methodData, flasAmmSellTokensIXs) {
|
2026-01-28 14:11:34 +08:00
|
|
|
txSignal, err = parseFlasAmmSell(tx, instructionIndex)
|
2026-04-28 18:26:43 +08:00
|
|
|
} else if matchFlasMethod(methodData, flasBonkBuyTokensIXs) {
|
2026-03-23 16:01:41 +08:00
|
|
|
txSignal, err = parseFlasBonkBuy(tx, instructionIndex)
|
2026-04-28 18:26:43 +08:00
|
|
|
} else if matchFlasMethod(methodData, flasBonkSellTokensIXs) {
|
2026-03-23 16:01:41 +08:00
|
|
|
txSignal, err = parseFlasBonkSell(tx, instructionIndex)
|
2026-01-28 14:11:34 +08:00
|
|
|
}
|
|
|
|
|
if txSignal != nil {
|
|
|
|
|
return TxSignalBatch{txSignal}, err
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
|
|
|
|
instruction := tx.Instructions[instructionIndex]
|
|
|
|
|
if len(instruction.Accounts) < 10 {
|
|
|
|
|
return nil, fmt.Errorf("accounts too short")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mint, err := tx.GetAccount(int(instruction.Accounts[9]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 18:26:43 +08:00
|
|
|
args, err := decodeFlasArgs(instruction.Data)
|
|
|
|
|
if err != nil {
|
2026-01-28 14:11:34 +08:00
|
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
|
|
|
}
|
2026-04-28 18:26:43 +08:00
|
|
|
token0Amount := formatTokenAmount(args.Amount1)
|
|
|
|
|
token0AmountUint64 := args.Amount1
|
|
|
|
|
if len(instruction.Accounts) == 52 {
|
|
|
|
|
token0Amount = decimal.Zero
|
|
|
|
|
token0AmountUint64 = 0
|
|
|
|
|
}
|
2026-01-28 14:11:34 +08:00
|
|
|
|
|
|
|
|
return &TxSignal{
|
|
|
|
|
TxHash: tx.Signatures[0].String(),
|
|
|
|
|
Label: "flas",
|
|
|
|
|
Maker: user.String(),
|
|
|
|
|
Token0Address: mint.String(),
|
|
|
|
|
Token1Address: wsolMint,
|
2026-04-28 18:26:43 +08:00
|
|
|
Token0Amount: token0Amount,
|
2026-01-28 14:11:34 +08:00
|
|
|
Token1Amount: formatSolAmount(args.Amount2),
|
|
|
|
|
Program: "PumpAMM",
|
|
|
|
|
Event: "sell",
|
|
|
|
|
IsToken2022: false,
|
|
|
|
|
IsMayhemMode: false,
|
|
|
|
|
ExactSOL: false,
|
|
|
|
|
Block: tx.Block,
|
2026-04-28 18:26:43 +08:00
|
|
|
Token0AmountUint64: token0AmountUint64,
|
2026-01-28 14:11:34 +08:00
|
|
|
Token1AmountUint64: args.Amount2,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
|
|
|
|
instruction := tx.Instructions[instructionIndex]
|
|
|
|
|
if len(instruction.Accounts) < 10 {
|
|
|
|
|
return nil, fmt.Errorf("accounts too short")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mint, err := tx.GetAccount(int(instruction.Accounts[9]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 18:26:43 +08:00
|
|
|
args, err := decodeFlasArgs(instruction.Data)
|
|
|
|
|
if err != nil {
|
2026-01-28 14:11:34 +08:00
|
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &TxSignal{
|
|
|
|
|
TxHash: tx.Signatures[0].String(),
|
|
|
|
|
Label: "flas",
|
|
|
|
|
Maker: user.String(),
|
|
|
|
|
Token0Address: mint.String(),
|
|
|
|
|
Token1Address: wsolMint,
|
|
|
|
|
Token0Amount: decimal.Zero,
|
|
|
|
|
Token1Amount: formatSolAmount(args.Amount1),
|
|
|
|
|
Program: "PumpAMM",
|
|
|
|
|
Event: "buy",
|
|
|
|
|
IsToken2022: false,
|
|
|
|
|
IsMayhemMode: false,
|
|
|
|
|
ExactSOL: true,
|
|
|
|
|
Block: tx.Block,
|
|
|
|
|
Token0AmountUint64: 0,
|
|
|
|
|
Token1AmountUint64: args.Amount1,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
|
|
|
|
instruction := tx.Instructions[instructionIndex]
|
2026-02-21 19:03:00 +08:00
|
|
|
if len(instruction.Accounts) < 11 {
|
2026-01-28 14:11:34 +08:00
|
|
|
return nil, fmt.Errorf("accounts too short")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:03:00 +08:00
|
|
|
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
2026-01-28 14:11:34 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 18:26:43 +08:00
|
|
|
args, err := decodeFlasArgs(instruction.Data)
|
|
|
|
|
if err != nil {
|
2026-01-28 14:11:34 +08:00
|
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &TxSignal{
|
|
|
|
|
TxHash: tx.Signatures[0].String(),
|
|
|
|
|
Label: "flas",
|
|
|
|
|
Maker: user.String(),
|
|
|
|
|
Token0Address: mint.String(),
|
|
|
|
|
Token1Address: wsolMint,
|
|
|
|
|
Token0Amount: formatTokenAmount(args.Amount1),
|
|
|
|
|
Token1Amount: formatSolAmount(args.Amount2),
|
|
|
|
|
Program: "Pump",
|
|
|
|
|
Event: "sell",
|
|
|
|
|
IsToken2022: false,
|
|
|
|
|
IsMayhemMode: false,
|
|
|
|
|
Block: tx.Block,
|
|
|
|
|
Token0AmountUint64: args.Amount1,
|
|
|
|
|
Token1AmountUint64: args.Amount2,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
|
|
|
|
instruction := tx.Instructions[instructionIndex]
|
2026-02-21 19:03:00 +08:00
|
|
|
if len(instruction.Accounts) < 11 {
|
2026-01-28 14:11:34 +08:00
|
|
|
return nil, fmt.Errorf("accounts too short")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:03:00 +08:00
|
|
|
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
2026-01-28 14:11:34 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-02-21 19:03:00 +08:00
|
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
2026-01-28 14:11:34 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-28 18:26:43 +08:00
|
|
|
args, err := decodeFlasArgs(instruction.Data)
|
|
|
|
|
if err != nil {
|
2026-01-28 14:11:34 +08:00
|
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &TxSignal{
|
|
|
|
|
TxHash: tx.Signatures[0].String(),
|
|
|
|
|
Label: "flas",
|
|
|
|
|
Maker: user.String(),
|
|
|
|
|
Token0Address: mint.String(),
|
|
|
|
|
Token1Address: wsolMint,
|
|
|
|
|
Token0Amount: formatTokenAmount(args.Amount2),
|
|
|
|
|
Token1Amount: formatSolAmount(args.Amount1),
|
|
|
|
|
Program: "Pump",
|
|
|
|
|
Event: "buy",
|
|
|
|
|
IsToken2022: false,
|
|
|
|
|
IsMayhemMode: false,
|
|
|
|
|
ExactSOL: true,
|
|
|
|
|
Block: tx.Block,
|
|
|
|
|
Token0AmountUint64: args.Amount2,
|
|
|
|
|
Token1AmountUint64: args.Amount1,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
2026-03-23 16:01:41 +08:00
|
|
|
|
|
|
|
|
func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
|
|
|
|
instruction := tx.Instructions[instructionIndex]
|
2026-04-28 18:26:43 +08:00
|
|
|
if len(instruction.Accounts) < 17 {
|
2026-03-23 16:01:41 +08:00
|
|
|
return nil, fmt.Errorf("accounts too short")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-23 16:08:55 +08:00
|
|
|
base, err := tx.GetAccount(int(instruction.Accounts[15]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
quote, err := tx.GetAccount(int(instruction.Accounts[16]))
|
2026-03-23 16:01:41 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-03-23 16:08:55 +08:00
|
|
|
|
|
|
|
|
if !quote.Equals(wrappedSOL) {
|
|
|
|
|
// just ignore this
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 18:26:43 +08:00
|
|
|
args, err := decodeFlasArgs(instruction.Data)
|
|
|
|
|
if err != nil {
|
2026-03-23 16:01:41 +08:00
|
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &TxSignal{
|
|
|
|
|
TxHash: tx.Signatures[0].String(),
|
|
|
|
|
Label: "flas",
|
|
|
|
|
Maker: user.String(),
|
2026-03-23 16:08:55 +08:00
|
|
|
Token0Address: base.String(),
|
2026-03-23 16:01:41 +08:00
|
|
|
Token1Address: wsolMint,
|
|
|
|
|
Token0Amount: formatTokenAmount(args.Amount2),
|
|
|
|
|
Token1Amount: formatSolAmount(args.Amount1),
|
|
|
|
|
Program: "RaydiumLaunchLab",
|
|
|
|
|
Event: "buy",
|
|
|
|
|
ExactSOL: true,
|
|
|
|
|
Block: tx.Block,
|
|
|
|
|
Token0AmountUint64: args.Amount2,
|
|
|
|
|
Token1AmountUint64: args.Amount1,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseFlasBonkSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
|
|
|
|
instruction := tx.Instructions[instructionIndex]
|
2026-04-28 18:26:43 +08:00
|
|
|
if len(instruction.Accounts) < 17 {
|
2026-03-23 16:01:41 +08:00
|
|
|
return nil, fmt.Errorf("accounts too short")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-23 16:08:55 +08:00
|
|
|
base, err := tx.GetAccount(int(instruction.Accounts[15]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
quote, err := tx.GetAccount(int(instruction.Accounts[16]))
|
2026-03-23 16:01:41 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-23 16:08:55 +08:00
|
|
|
if !quote.Equals(wrappedSOL) {
|
|
|
|
|
// just ignore this
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 18:26:43 +08:00
|
|
|
args, err := decodeFlasArgs(instruction.Data)
|
|
|
|
|
if err != nil {
|
2026-03-23 16:01:41 +08:00
|
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &TxSignal{
|
|
|
|
|
TxHash: tx.Signatures[0].String(),
|
|
|
|
|
Label: "flas",
|
|
|
|
|
Maker: user.String(),
|
2026-03-23 16:08:55 +08:00
|
|
|
Token0Address: base.String(),
|
2026-03-23 16:01:41 +08:00
|
|
|
Token1Address: wsolMint,
|
|
|
|
|
Token0Amount: formatTokenAmount(args.Amount1),
|
|
|
|
|
Token1Amount: formatSolAmount(args.Amount2),
|
|
|
|
|
Program: "RaydiumLaunchLab",
|
|
|
|
|
Event: "sell",
|
|
|
|
|
Block: tx.Block,
|
|
|
|
|
Token0AmountUint64: args.Amount1,
|
|
|
|
|
Token1AmountUint64: args.Amount2,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|