319 lines
9.1 KiB
Go
319 lines
9.1 KiB
Go
package shreder
|
|
|
|
import (
|
|
"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}
|
|
)
|
|
|
|
type flasArgs struct {
|
|
Amount1 uint64
|
|
Amount2 uint64
|
|
Placeholder [3]uint8
|
|
}
|
|
|
|
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]
|
|
//if !matchMethod(methodData, flasBuyTokensIX) {
|
|
// return nil, nil
|
|
//}
|
|
var (
|
|
err error
|
|
txSignal *TxSignal
|
|
)
|
|
if matchMethod(methodData, flasBuyTokensIX) {
|
|
txSignal, err = parseFlasBuy(tx, instructionIndex)
|
|
} else if matchMethod(methodData, flasSellTokensIX) {
|
|
txSignal, err = parseFlasSell(tx, instructionIndex)
|
|
} else if matchMethod(methodData, flasAmmBuyTokensIX) {
|
|
txSignal, err = parseFlasAmmBuy(tx, instructionIndex)
|
|
} else if matchMethod(methodData, flasAmmSellTokensIX) {
|
|
txSignal, err = parseFlasAmmSell(tx, instructionIndex)
|
|
} else if matchMethod(methodData, flasBonkBuyTokensIX) {
|
|
txSignal, err = parseFlasBonkBuy(tx, instructionIndex)
|
|
} else if matchMethod(methodData, flasBonkSellTokensIX) {
|
|
txSignal, err = parseFlasBonkSell(tx, instructionIndex)
|
|
}
|
|
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
|
|
}
|
|
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
|
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: "PumpAMM",
|
|
Event: "sell",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
ExactSOL: false,
|
|
Block: tx.Block,
|
|
Token0AmountUint64: args.Amount1,
|
|
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
|
|
}
|
|
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
|
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]
|
|
if len(instruction.Accounts) < 11 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
|
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]
|
|
if len(instruction.Accounts) < 11 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
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 {
|
|
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
|
|
}
|
|
|
|
func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
|
instruction := tx.Instructions[instructionIndex]
|
|
if len(instruction.Accounts) < 16 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
base, err := tx.GetAccount(int(instruction.Accounts[15]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
quote, err := tx.GetAccount(int(instruction.Accounts[16]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !quote.Equals(wrappedSOL) {
|
|
// just ignore this
|
|
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 {
|
|
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: base.String(),
|
|
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]
|
|
if len(instruction.Accounts) < 16 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
base, err := tx.GetAccount(int(instruction.Accounts[15]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
quote, err := tx.GetAccount(int(instruction.Accounts[16]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !quote.Equals(wrappedSOL) {
|
|
// just ignore this
|
|
return nil, nil
|
|
}
|
|
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
|
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: base.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(args.Amount1),
|
|
Token1Amount: formatSolAmount(args.Amount2),
|
|
Program: "RaydiumLaunchLab",
|
|
Event: "sell",
|
|
Block: tx.Block,
|
|
Token0AmountUint64: args.Amount1,
|
|
Token1AmountUint64: args.Amount2,
|
|
}, nil
|
|
}
|