Files
libsam/pkg/shreder/txparser.go

1902 lines
54 KiB
Go
Raw Normal View History

2025-12-30 11:03:11 +08:00
package shreder
2025-12-26 10:57:37 +08:00
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
2026-01-23 17:58:59 +08:00
"slices"
2026-01-07 11:18:02 +08:00
"strings"
2026-01-07 21:15:54 +08:00
"sync"
"time"
2025-12-26 10:57:37 +08:00
"github.com/gagliardetto/solana-go"
2025-12-30 11:03:11 +08:00
"github.com/mr-tron/base58"
2025-12-26 10:57:37 +08:00
"github.com/near/borsh-go"
"github.com/shopspring/decimal"
)
const (
wsolMint = "So11111111111111111111111111111111111111112"
tokenProgram = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
)
// program ids
2025-12-30 11:03:11 +08:00
var (
pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
2025-12-30 11:03:11 +08:00
// has no sell function with pump and pump.amm program
azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB")
2025-12-30 11:03:11 +08:00
// only buy function with pump program
f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq")
2025-12-30 11:03:11 +08:00
// only pump.fun function
photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW")
2025-12-26 10:57:37 +08:00
pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
2025-12-26 10:57:37 +08:00
boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM")
2025-12-26 10:57:37 +08:00
qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7")
2025-12-30 11:03:11 +08:00
// only buy function with pump program
fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK")
2025-12-30 11:03:11 +08:00
flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
2025-12-30 11:03:11 +08:00
terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
2026-01-06 16:42:07 +08:00
jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
2026-01-08 12:44:47 +08:00
gmgnProgramID = solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb")
2026-01-08 16:07:57 +08:00
bonkProgramID = solana.MustPublicKeyFromBase58("BBRouter1cVunVXvkcqeKkZQcBK7ruan37PPm3xzWaXD")
2025-12-26 10:57:37 +08:00
)
2026-01-06 16:42:07 +08:00
type AccountNotFoundError struct {
Index int
Len int
}
func NewAccountNotFoundError(i, l int) error {
return &AccountNotFoundError{i, l}
}
func (e AccountNotFoundError) Error() string {
return fmt.Sprintf("account index %d out of range, len=%d", e.Index, e.Len)
}
2025-12-30 11:03:11 +08:00
// instruction discriminators
2025-12-26 10:57:37 +08:00
var (
2025-12-30 11:03:11 +08:00
pumpCreateCoinIX = []byte{24, 30, 200, 40, 5, 28, 7, 119}
pumpCreateCoinV2IX = []byte{214, 144, 76, 236, 95, 139, 49, 180}
pumpExtendedSellIX = []byte{51, 230, 133, 164, 1, 127, 131, 173}
pumpBuyTokensIX = []byte{102, 6, 61, 18, 1, 218, 235, 234}
pumpBuyV2TokensIX = []byte{56, 252, 116, 8, 158, 223, 205, 95}
azczBuyTokensIX = []byte{11}
azczAmmBuyTokensIX = []byte{0xf}
f5tfBuyTokensIX = []byte{0}
flasBuyTokensIX = []byte{0x00, 0x1, 0x4}
flasSellTokensIX = []byte{0x01, 0x1, 0x3}
flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2}
flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2}
pumpAmmBuyTokensV2IX = []byte{198, 46, 21, 82, 180, 217, 232, 112}
pumpAmmBuyTokensIX = []byte{102, 6, 61, 18, 1, 218, 235, 234}
pumpAmmSellTokensIX = []byte{51, 230, 133, 164, 1, 127, 131, 173}
qtkvBuyTokensIX = []byte{0x02}
qtkvSellTokensIX = []byte{0x03}
qtkvAmmSellTokensIX = []byte{0x05}
2025-12-26 10:57:37 +08:00
boboBuyPumpTokensIX = []byte{0xff, 0xe7, 0x11, 0x53, 0x15, 0xc5, 0xc3, 0xdf}
fjszBuyTokensIX = []byte{0xe7, 0x3f, 0x99, 0x83, 0xf3, 0xed, 0xe3, 0x3c}
photonBuyPumpTokensIX = []byte{0x52, 0xe1, 0x77, 0xe7, 0x4e, 0x1d, 0x2d, 0x46}
photonSwapPumpAmmIX = []byte{0x2c, 0x77, 0xaf, 0xda, 0xc7, 0x4d, 0xc4, 0xeb}
2025-12-30 11:03:11 +08:00
terminalBuyTokensIX = []byte{0xa6, 0x54, 0x14, 0x96, 0x9f, 0x77, 0x59, 0xca}
terminalSellTokensIX = []byte{0xbe, 0x84, 0xa2, 0x96, 0x93, 0x7c, 0xf8, 0x6b}
terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1}
gmgnBuyTokensIX = []byte{0x66, 0x06, 0x3d, 0x12, 0x01, 0xda, 0xeb, 0xea}
2026-01-08 16:07:57 +08:00
bonkBuyAndSellTokensIX = []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}
2025-12-26 10:57:37 +08:00
)
2026-01-23 17:58:59 +08:00
type addressTableLookup struct {
AccountKeyOffset int
WriteOffset int
WriteLen int
ReadOffset int
ReadLen int
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
type TransactionGetter interface {
GetAccount(idx uint8) (solana.PublicKey, error)
Signatures() string
Block() uint64
GetAddressTableLookups(idx int) (account solana.PublicKey, write []uint8, read []uint8, err error)
GetAddressTablesLookupsLen() int
GetInstruction(idx int) (program solana.PublicKey, accounts []uint8, data []byte, err error)
GetInstructionLen() int
GetStaticAccountKeys(idx uint8) (account solana.PublicKey, err error)
GetStaticAccountKeysLen() uint8
}
type FillableTransaction interface {
FillLookupTable(account solana.PublicKey)
}
func (tx *versionedTransaction) FillLookupTable(account solana.PublicKey) {
tx.TableLookups = append(tx.TableLookups, account)
}
func (tx *versionedTransaction) GetAccount(idx uint8) (solana.PublicKey, error) {
idxMax := tx.staticAccountKeys + uint8(len(tx.TableLookups))
if idx < 0 || idx >= idxMax {
return solana.PublicKey{}, fmt.Errorf("account index %d out of range, len=%d", idx, idxMax)
}
if idx < tx.staticAccountKeys {
return tx.GetStaticAccountKeys(idx)
}
idx -= tx.staticAccountKeys
if idx < uint8(len(tx.TableLookups)) {
return tx.TableLookups[idx], nil
}
return solana.PublicKey{}, fmt.Errorf("account index %d out of range, len=%d", idx, len(tx.TableLookups))
}
func (tx *versionedTransaction) GetInstructionLen() int {
return tx.instructions
}
func (tx *versionedTransaction) GetInstruction(idx int) (program solana.PublicKey, accounts []uint8, data []byte, err error) {
if idx < 0 || idx >= tx.instructions {
err = fmt.Errorf("instruction index %d out of range, len=%d", idx, tx.instructions)
return
}
if tx.bind != nil {
program, err = tx.GetAccount(uint8(tx.bind.Transaction.Message.Instructions[idx].ProgramIdIndex))
if err != nil {
return
}
accounts = tx.bind.Transaction.Message.Instructions[idx].Accounts
data = tx.bind.Transaction.Message.Instructions[idx].Data
} else if len(tx.bindArray) > 0 {
instr := tx.Instrs[idx]
program, err = tx.GetAccount(instr.ProgramIDIndex)
if err != nil {
return
}
if instr.AccountsLen > 0 {
accounts = tx.bindArray[instr.AccountsOffset : instr.AccountsOffset+instr.AccountsLen]
}
if instr.DataLen > 0 {
data = tx.bindArray[instr.DataOffset : instr.DataOffset+instr.DataLen]
}
} else {
err = fmt.Errorf("instruction index %d out of range, len=%d", idx, len(tx.bindArray))
return
}
return
}
func (tx *versionedTransaction) GetAddressTableLookups(idx int) (account solana.PublicKey, write []uint8, read []uint8, err error) {
if idx < 0 || idx >= tx.addressTableLookups {
err = fmt.Errorf("instruction index %d out of range, len=%d", idx, tx.instructions)
return
}
if tx.bind != nil {
account = solana.PublicKey(tx.bind.Transaction.Message.AddressTableLookups[idx].AccountKey)
write = tx.bind.Transaction.Message.AddressTableLookups[idx].WritableIndexes
read = tx.bind.Transaction.Message.AddressTableLookups[idx].ReadonlyIndexes
} else if len(tx.bindArray) > 0 {
lookup := tx.ATL[idx]
copy(account[:], tx.bindArray[lookup.AccountKeyOffset:lookup.AccountKeyOffset+32])
if lookup.WriteLen > 0 {
write = tx.bindArray[lookup.WriteOffset : lookup.WriteOffset+lookup.WriteLen]
}
if lookup.ReadLen > 0 {
read = tx.bindArray[lookup.ReadOffset : lookup.ReadOffset+lookup.ReadLen]
}
} else {
err = fmt.Errorf("instruction index %d out of range, len=%d", idx, len(tx.bindArray))
}
return
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
func (tx *versionedTransaction) GetAddressTablesLookupsLen() int {
return tx.addressTableLookups
}
func (tx *versionedTransaction) GetStaticAccountKeys(idx uint8) (account solana.PublicKey, err error) {
if idx < 0 || idx >= tx.staticAccountKeys {
return solana.PublicKey{}, fmt.Errorf("static account index %d out of range, len=%d", idx, tx.staticAccountKeys)
}
if tx.bind != nil {
var key solana.PublicKey
copy(key[:], tx.bind.Transaction.Message.AccountKeys[idx])
return key, nil
} else if len(tx.bindArray) > 0 {
start := tx.staticAccountKeysOffset + int(idx)*32
end := start + 32
var key solana.PublicKey
copy(key[:], tx.bindArray[start:end])
return key, nil
} else {
return solana.PublicKey{}, fmt.Errorf("static account index %d out of range, len=%d", idx, len(tx.bindArray))
}
}
func (tx *versionedTransaction) GetStaticAccountKeysLen() uint8 {
return tx.staticAccountKeys
2025-12-26 10:57:37 +08:00
}
type versionedTransaction struct {
2026-01-23 17:58:59 +08:00
bind *SubscribeUpdateTransaction
bindArray []byte
signatures int
signaturesOffset int
instructions int
addressTableLookups int
staticAccountKeys uint8
staticAccountKeysOffset int
Instrs []compiledInstruction
ATL []addressTableLookup
TableLookups []solana.PublicKey
block uint64
Time time.Time
}
func (tx *versionedTransaction) Signatures() string {
if tx.bind != nil {
return base58.Encode(tx.bind.Transaction.Signatures[0])
} else if len(tx.bindArray) > 0 && tx.signatures > 0 {
start := tx.signaturesOffset
end := start + 64
return base58.Encode(tx.bindArray[start:end])
}
return ""
}
func (tx *versionedTransaction) Block() uint64 {
return tx.block
2025-12-26 10:57:37 +08:00
}
type pumpExtendedSellArgs struct {
Amount uint64
MinSolOutput uint64
}
type pumpBuyArgs struct {
Amount uint64
MaxSolCost uint64
}
type pumpCreateCoinV2Args struct {
Name string
Symbol string
Uri string
Creator solana.PublicKey
IsMayhemMode bool
}
type azczBuyArgs struct {
SolAmount uint64
TokenAmount uint64
}
type f5tfBuyArgs struct {
SolAmount uint64
TokenAmount uint64
}
2026-01-07 13:16:22 +08:00
type flasArgs struct {
Amount1 uint64
Amount2 uint64
2025-12-26 10:57:37 +08:00
Placeholder [3]uint8
}
type photonBuyPumpArgs struct {
Timestamp uint64
SolAmount uint64
TokenAmount uint64
Fee uint64
}
type photonSwapPumpAmmArgs struct {
FromAmount uint64
ToAmount uint64
}
type pumpAmmBuyArgs struct {
Amount uint64
MaxSolCost uint64
}
type boboBuyArgs struct {
Placeholder1 uint64
Placeholder2 uint64
SolAmount uint64
Placeholder3 uint64
Placeholder4 uint64
Placeholder5 uint64
Placeholder6 uint64
}
type qtkvBuyArgs struct {
Placeholder uint64
TokenNumber uint64
SolAmount uint64
}
type fjszBuyArgs struct {
SolAmount uint64
TokenAmount uint64
}
2026-01-07 21:15:54 +08:00
var (
versionedPool = sync.Pool{}
)
2026-01-23 17:58:59 +08:00
type compiledInstruction struct {
ProgramIDIndex uint8
AccountsLen int
AccountsOffset int
DataOffset int
DataLen int
2026-01-07 21:15:54 +08:00
}
func requireVersionedPool() *versionedTransaction {
v := versionedPool.Get()
if v == nil {
return &versionedTransaction{
2026-01-23 17:58:59 +08:00
TableLookups: make([]solana.PublicKey, 0, 16),
Instrs: make([]compiledInstruction, 0, 16),
ATL: make([]addressTableLookup, 0, 4),
2026-01-07 21:15:54 +08:00
}
}
return v.(*versionedTransaction)
}
func releaseVersionedPool(v *versionedTransaction) {
if v == nil {
return
}
2026-01-23 17:58:59 +08:00
v.TableLookups = v.TableLookups[:0]
v.Instrs = v.Instrs[:0]
v.ATL = v.ATL[:0]
v.bind = nil
v.bindArray = nil
v.signaturesOffset = 0
v.signatures = 0
v.instructions = 0
v.addressTableLookups = 0
v.staticAccountKeys = 0
v.staticAccountKeysOffset = 0
v.block = 0
versionedPool.Put(v)
}
var VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
type Stats struct {
Start time.Time
FEC time.Time
Decoded time.Time
Filter time.Time
Done time.Time
DataLen int
TxCount int
TxOffset int
}
// ParseEntries mirrors the Rust parse_transaction entry point.
func ParseEntries(slot uint64, entries []byte, loader *AddressTables, txCh chan<- TxSignal, stats bool) {
var stat Stats
if stats {
stat.Start = time.Now()
stat.FEC = time.Now()
2026-01-07 21:15:54 +08:00
}
2026-01-23 17:58:59 +08:00
versioned, err := entriesToVersionedTransaction(slot, newConstArray(entries))
if err != nil || len(versioned) == 0 {
if err != nil {
logger.Warn("decoder: failed to parse entries to versioned transactions", "slot", slot, "error", err)
for _, v := range versioned {
releaseVersionedPool(v)
}
}
return
2026-01-07 21:15:54 +08:00
}
2026-01-23 17:58:59 +08:00
// logger.Info("parsed entries to versioned transactions", "time", stat.Start.String(), "slot", slot, "txCount", len(versioned))
if stats {
stat.DataLen = len(entries)
stat.TxCount = len(versioned)
}
//defer func() {
// for _, v := range versioned {
// releaseVersionedPool(v)
// }
//}()
stat.Decoded = time.Now()
for k, vTx := range versioned {
//if vTx.instructions >= 1 {
// programKey, _, _, _ := vTx.GetInstruction(0)
// if programKey.Equals(VoteProgram) && len(vTx.TableLookups) == 0 {
// releaseVersionedPool(vTx)
// return
// }
//}
go func(v *versionedTransaction, st Stats) {
defer func() {
releaseVersionedPool(v)
}()
include := false
for i := uint8(0); i < v.staticAccountKeys; i++ {
key, _ := v.GetAccount(i)
_, include = slices.BinarySearchFunc(parseProgram, key, func(key solana.PublicKey, key2 solana.PublicKey) int {
return bytes.Compare(key[:], key2[:])
})
if include {
break
}
}
if !include {
return
}
st.Filter = time.Now()
st.TxOffset = k
// logger.Info("decode time", "tx", vTx.Signatures(), "s", time.Since(st.Start).String(), "idx", k, "txCount", len(versioned), "datLen", len(entries))
parseTransaction(st, v, loader, txCh)
}(vTx, stat)
}
return
2026-01-07 21:15:54 +08:00
}
2025-12-26 10:57:37 +08:00
// ParseTransaction mirrors the Rust parse_transaction entry point.
2026-01-23 17:58:59 +08:00
func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables, txCh chan<- TxSignal, stats bool) {
var stat Stats
2026-01-07 21:15:54 +08:00
if stats {
2026-01-23 17:58:59 +08:00
stat.Start = time.Now()
2026-01-07 21:15:54 +08:00
}
2025-12-26 10:57:37 +08:00
versioned, err := toVersionedTransaction(update)
2026-01-23 17:58:59 +08:00
if err != nil || versioned == nil || versioned.signatures == 0 {
return
2025-12-26 10:57:37 +08:00
}
2026-01-07 21:15:54 +08:00
defer func() {
releaseVersionedPool(versioned)
}()
2026-01-23 17:58:59 +08:00
parseTransaction(stat, versioned, loader, txCh)
}
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
type Handler struct {
Func func(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error)
Label string
}
var (
parsedMap = map[solana.PublicKey]Handler{
pumpProgramID: Handler{parsePumpInstruction, "pump"},
azczProgramID: Handler{parseAzczInstruction, "azcz"},
f5tfProgramID: Handler{parseF5tfInstruction, "f5tf"},
flasProgramID: Handler{parseFlasInstruction, "flas"},
photonProgramID: Handler{parsePhotonInstruction, "photon"},
pumpAmmProgramID: Handler{parsePumpAmmInstruction, "pumpamm"},
boboProgramID: Handler{parseBoboInstruction, "bobo"},
qtkvProgramID: Handler{parseQtkvInstruction, "qtkv"},
fjszProgramID: Handler{parseFjszInstruction, "fjsz"},
terminalProgramID: Handler{parseTermInstruction, "terminal"},
jupiterV6ProgramID: Handler{parseJupiterV6Instruction, "jupiterv6"},
okxDexRouteV2ProgramID: Handler{parseOkxDexRouteV2Instruction, "okxdexroutev2"},
dflowProgramID: Handler{parseDFlowInstruction, "dflow"},
gmgnProgramID: Handler{parseGMGNInstruction, "gmgn"},
bonkProgramID: Handler{parseBonkInstruction, "bonk"},
}
parseProgram []solana.PublicKey
)
func init() {
for account := range parsedMap {
parseProgram = append(parseProgram, account)
}
slices.SortFunc(parseProgram, func(a, b solana.PublicKey) int {
return bytes.Compare(a[:], b[:])
})
}
func parseTransaction(stat Stats, versioned *versionedTransaction, loader *AddressTables, parsed chan<- TxSignal) {
// staticKeys := versioned.Message.StaticAccountKeys
if loader != nil && versioned.addressTableLookups > 0 {
2026-01-06 16:42:07 +08:00
lookupTableOk := true
2026-01-23 17:58:59 +08:00
for i := 0; i < versioned.addressTableLookups; i++ {
lookups, idxs, _, err := versioned.GetAddressTableLookups(i)
if err != nil {
continue
}
if len(idxs) == 0 {
2026-01-06 16:42:07 +08:00
continue
}
2026-01-23 17:58:59 +08:00
lookupTableOk = loader.FillToTx(versioned, lookups, idxs)
2026-01-08 11:57:57 +08:00
if !lookupTableOk {
2026-01-05 12:45:32 +08:00
break
}
2026-01-06 16:42:07 +08:00
}
if lookupTableOk {
2026-01-23 17:58:59 +08:00
for i := 0; i < versioned.addressTableLookups; i++ {
lookups, _, idxs, err := versioned.GetAddressTableLookups(i)
if err != nil {
continue
}
if len(idxs) == 0 {
2026-01-06 16:42:07 +08:00
continue
}
2026-01-23 17:58:59 +08:00
lookupTableOk = loader.FillToTx(versioned, lookups, idxs)
2026-01-08 11:57:57 +08:00
if !lookupTableOk {
2026-01-06 16:42:07 +08:00
break
}
2026-01-05 12:45:32 +08:00
}
}
2026-01-07 21:15:54 +08:00
// versioned.Message.StaticAccountKeys = staticKeys
2026-01-05 12:45:32 +08:00
}
2026-01-23 17:58:59 +08:00
//instructions := versioned.GetInstruction()
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
for i := 0; i < versioned.GetInstructionLen(); i++ {
programID, accounts, data, err := versioned.GetInstruction(i)
if err != nil {
2025-12-26 10:57:37 +08:00
continue
}
2026-01-23 17:58:59 +08:00
handler, ok := parsedMap[programID]
if !ok {
continue
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
txRes, err := handler.Func(versioned, accounts, data)
appendParsed(stat, parsed, txRes, err, versioned, handler.Label)
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
return
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
func appendParsed(stat Stats, txCh chan<- TxSignal, parsed *TxSignal, err error, tx TransactionGetter, label string) {
2025-12-26 10:57:37 +08:00
if err != nil {
2026-01-07 11:18:02 +08:00
if !strings.HasPrefix(err.Error(), "account index") {
2026-01-23 17:58:59 +08:00
logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", tx.Signatures())
2026-01-07 11:18:02 +08:00
}
2026-01-23 17:58:59 +08:00
return
2025-12-26 10:57:37 +08:00
}
if parsed != nil {
2026-01-07 21:15:54 +08:00
parsed.Label = label
2026-01-23 17:58:59 +08:00
if !stat.Start.IsZero() {
stat.Done = time.Now()
parsed.Stats = stat
2026-01-07 21:15:54 +08:00
}
2026-01-23 17:58:59 +08:00
txCh <- *parsed
} else {
// logger.Debug("txparser: no parsed result", "label", label, "tx_hash", tx.Signatures())
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
return
2025-12-26 10:57:37 +08:00
}
2025-12-30 11:03:11 +08:00
func toVersionedTransaction(update *SubscribeUpdateTransaction) (*versionedTransaction, error) {
2025-12-26 10:57:37 +08:00
if update == nil || update.Transaction == nil || update.Transaction.Message == nil {
return nil, fmt.Errorf("transaction is nil")
}
protoTx := update.Transaction
msg := protoTx.Message
2026-01-07 21:15:54 +08:00
versioned := requireVersionedPool()
2026-01-23 17:58:59 +08:00
versioned.signatures = len(protoTx.Signatures)
versioned.instructions = len(msg.Instructions)
versioned.addressTableLookups = len(msg.AddressTableLookups)
versioned.staticAccountKeys = uint8(len(msg.AccountKeys))
versioned.bind = update
versioned.block = update.GetSlot()
2026-01-07 21:15:54 +08:00
return versioned, nil
2025-12-26 10:57:37 +08:00
}
func formatTokenAmount(amount uint64) decimal.Decimal {
val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0)
return val.Div(decimal.NewFromInt(1_000_000))
}
func formatSolAmount(lamports uint64) decimal.Decimal {
val := decimal.NewFromBigInt(new(big.Int).SetUint64(lamports), 0)
return val.Div(decimal.NewFromInt(1_000_000_000))
}
2026-01-23 17:58:59 +08:00
func parsePumpInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(data) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if matchMethod(data[0:8], pumpBuyV2TokensIX) || matchMethod(data[0:8], pumpBuyTokensIX) {
return parsePumpBuy(tx, accounts, data)
} else if matchMethod(data[0:8], pumpExtendedSellIX) {
return parsePumpSell(tx, accounts, data)
} else if matchMethod(data[0:8], pumpCreateCoinIX) {
return parsePumpCreate(tx, accounts, data)
} else if matchMethod(data[0:8], pumpCreateCoinV2IX) {
return parsePumpCreateV2(tx, accounts, data)
2025-12-26 10:57:37 +08:00
}
2025-12-30 11:03:11 +08:00
return nil, nil
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
func parsePumpCreate(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
creator, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "pump",
2025-12-26 10:57:37 +08:00
Maker: creator.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: decimal.Zero,
Program: "Pump",
Event: "create",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: 0,
Token1AmountUint64: 0,
}, nil
}
2026-01-23 17:58:59 +08:00
func parsePumpCreateV2(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
if len(data) < 8 {
return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
tokenProgramKey, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
var args pumpCreateCoinV2Args
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[8:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse create coin v2 args: %w", err)
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "pump",
2025-12-26 10:57:37 +08:00
Maker: args.Creator.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: decimal.Zero,
Program: "Pump",
Event: "create",
IsToken2022: tokenProgramKey.String() != tokenProgram,
IsMayhemMode: args.IsMayhemMode,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: 0,
Token1AmountUint64: 0,
}, nil
}
func decodePumpBuyArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 {
2026-01-05 14:38:02 +08:00
return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
var args pumpBuyArgs
if err := borsh.Deserialize(&args, data[8:]); err == nil {
return args.Amount, args.MaxSolCost, nil
}
if len(data) >= 24 {
amount := binary.LittleEndian.Uint64(data[8:16])
maxSol := binary.LittleEndian.Uint64(data[16:24])
return amount, maxSol, nil
}
return 0, 0, fmt.Errorf("failed to parse buy tokens args")
}
2026-01-23 17:58:59 +08:00
func parsePumpBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
amount, sol, err := decodePumpBuyArgs(data)
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
exactIn := false
2026-01-23 17:58:59 +08:00
if matchMethod(data, pumpBuyV2TokensIX) {
2025-12-30 11:03:11 +08:00
temp := amount
amount = sol
sol = temp
exactIn = true
}
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(accounts) < 7 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2])
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
buyer, err := tx.GetAccount(accounts[6])
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "pump",
2025-12-26 10:57:37 +08:00
Maker: buyer.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
2025-12-30 11:03:11 +08:00
Token1Amount: formatSolAmount(sol),
2025-12-26 10:57:37 +08:00
Program: "Pump",
Event: "buy",
2025-12-30 11:03:11 +08:00
ExactSOL: exactIn,
2025-12-26 10:57:37 +08:00
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: amount,
2025-12-30 11:03:11 +08:00
Token1AmountUint64: sol,
2025-12-26 10:57:37 +08:00
}, nil
}
func decodePumpSellArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 {
2026-01-05 14:38:02 +08:00
return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
var args pumpExtendedSellArgs
if err := borsh.Deserialize(&args, data[8:]); err == nil {
return args.Amount, args.MinSolOutput, nil
}
if len(data) >= 24 {
amount := binary.LittleEndian.Uint64(data[8:16])
minSol := binary.LittleEndian.Uint64(data[16:24])
return amount, minSol, nil
}
return 0, 0, fmt.Errorf("failed to parse sell tokens args")
}
2026-01-23 17:58:59 +08:00
func parsePumpSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
amount, minSol, err := decodePumpSellArgs(data)
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
if len(accounts) < 7 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2])
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
seller, err := tx.GetAccount(accounts[6])
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "pump",
2025-12-26 10:57:37 +08:00
Maker: seller.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(minSol),
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: amount,
Token1AmountUint64: minSol,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseAzczInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if matchMethod(data, azczBuyTokensIX) {
return parseAzczBuy(tx, accounts, data)
} else if matchMethod(data, azczAmmBuyTokensIX) {
return parseAzczAmmBuy(tx, accounts, data)
2025-12-26 10:57:37 +08:00
}
2025-12-30 11:03:11 +08:00
return nil, nil
}
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
func parseAzczAmmBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[3]) // getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
if len(data) < 17 {
return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(data))
2025-12-30 11:03:11 +08:00
}
2026-01-23 17:58:59 +08:00
solAmount := binary.LittleEndian.Uint64(data[1:9])
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "azcz",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: 0,
Token1AmountUint64: solAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseAzczBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
if len(data) < 2 {
return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
var args azczBuyArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[1:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "azcz",
2025-12-26 10:57:37 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: args.SolAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseF5tfInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if !matchMethod(data, f5tfBuyTokensIX) {
2025-12-26 10:57:37 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
if len(accounts) < 7 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
if len(data) < 2 {
return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
var args f5tfBuyArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[1:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "f5tf",
2025-12-26 10:57:37 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: args.SolAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseFlasInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if len(data) == 10 && data[0] == 1 {
2026-01-05 14:38:02 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
if len(data) < 20 {
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(data))
2025-12-30 11:03:11 +08:00
}
2026-01-23 17:58:59 +08:00
methodData := data[17:20]
2026-01-07 13:16:22 +08:00
//if !matchMethod(methodData, flasBuyTokensIX) {
// return nil, nil
//}
if matchMethod(methodData, flasBuyTokensIX) {
2026-01-23 17:58:59 +08:00
return parseFlasBuy(tx, accounts, data)
2025-12-30 11:03:11 +08:00
} else if matchMethod(methodData, flasSellTokensIX) {
2026-01-23 17:58:59 +08:00
return parseFlasSell(tx, accounts, data)
2025-12-30 11:03:11 +08:00
} else if matchMethod(methodData, flasAmmBuyTokensIX) {
2026-01-23 17:58:59 +08:00
return parseFlasAmmBuy(tx, accounts, data)
2025-12-30 11:03:11 +08:00
} else if matchMethod(methodData, flasAmmSellTokensIX) {
2026-01-23 17:58:59 +08:00
return parseFlasAmmSell(tx, accounts, data)
2025-12-30 11:03:11 +08:00
}
return nil, nil
}
2026-01-23 17:58:59 +08:00
func parseFlasAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 10 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
}
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[9]) //getStaticKey(staticKeys, int(instruction.Accounts[9]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[1]) // getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-07 13:16:22 +08:00
var args flasArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[1:]); err != nil {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "flas",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
2026-01-07 13:24:16 +08:00
Token0Amount: formatTokenAmount(args.Amount1),
2026-01-07 13:16:22 +08:00
Token1Amount: formatSolAmount(args.Amount2),
Program: "PumpAMM",
2025-12-30 11:03:11 +08:00
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
2026-01-07 13:16:22 +08:00
ExactSOL: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2026-01-07 13:16:22 +08:00
Token0AmountUint64: args.Amount1,
Token1AmountUint64: args.Amount2,
2025-12-30 11:03:11 +08:00
}, nil
}
2026-01-23 17:58:59 +08:00
func parseFlasAmmBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 10 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[9]) //getStaticKey(staticKeys, int(instruction.Accounts[9]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[1]) // getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-07 13:16:22 +08:00
var args flasArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[1:]); err != nil {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "flas",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
2026-01-07 13:16:22 +08:00
Token1Amount: formatSolAmount(args.Amount1),
Program: "PumpAMM",
Event: "buy",
2025-12-30 11:03:11 +08:00
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: 0,
2026-01-07 13:16:22 +08:00
Token1AmountUint64: args.Amount1,
2025-12-30 11:03:11 +08:00
}, nil
}
2026-01-23 17:58:59 +08:00
func parseFlasSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 9 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[8]) //getStaticKey(staticKeys, int(instruction.Accounts[8]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-07 13:16:22 +08:00
var args flasArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[1:]); err != nil {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "flas",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
2026-01-07 13:16:22 +08:00
Token0Amount: formatTokenAmount(args.Amount1),
Token1Amount: formatSolAmount(args.Amount2),
2025-12-30 11:03:11 +08:00
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2026-01-07 13:16:22 +08:00
Token0AmountUint64: args.Amount1,
Token1AmountUint64: args.Amount2,
2025-12-30 11:03:11 +08:00
}, nil
}
2026-01-23 17:58:59 +08:00
func parseFlasBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 9 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[8]) //getStaticKey(staticKeys, int(instruction.Accounts[8]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
if len(data) > 20 {
data = data[:20]
}
2026-01-07 13:16:22 +08:00
var args flasArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[1:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "flas",
2025-12-26 10:57:37 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
2026-01-07 13:16:22 +08:00
Token0Amount: formatTokenAmount(args.Amount2),
Token1Amount: formatSolAmount(args.Amount1),
2025-12-26 10:57:37 +08:00
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2025-12-30 11:03:11 +08:00
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2026-01-07 13:16:22 +08:00
Token0AmountUint64: args.Amount2,
Token1AmountUint64: args.Amount1,
2025-12-26 10:57:37 +08:00
}, nil
}
2026-01-23 17:58:59 +08:00
func parseGMGNInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if len(data) < 8 {
return nil, nil
}
2026-01-23 17:58:59 +08:00
if matchMethod(data, gmgnBuyTokensIX) {
return parseGMGNBuy(tx, accounts, data)
}
return nil, nil
}
2026-01-23 17:58:59 +08:00
func parseGMGNBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
if len(data) < 24 {
return nil, fmt.Errorf("data too short for gmgn buy args, len=%d", len(data))
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
solAmount := binary.LittleEndian.Uint64(data[8:16])
tokenAmount := binary.LittleEndian.Uint64(data[16:24])
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
Label: "gmgn",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parsePhotonInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if len(data) < 8 {
2025-12-26 10:57:37 +08:00
return nil, nil
}
switch {
2026-01-23 17:58:59 +08:00
case bytes.Equal(data[:8], photonBuyPumpTokensIX):
return parsePhotonBuy(tx, accounts, data)
case bytes.Equal(data[:8], photonSwapPumpAmmIX):
return parsePhotonSwap(tx, accounts, data)
2025-12-26 10:57:37 +08:00
default:
return nil, nil
}
}
2026-01-23 17:58:59 +08:00
func parsePhotonBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
if len(data) < 16 {
return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
var args photonBuyPumpArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[8:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
solAmount := args.SolAmount * (100000000 - 1234568) / 100000000
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "photon",
2025-12-26 10:57:37 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-08 16:25:34 +08:00
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parsePhotonSwap(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
if len(data) < 16 {
return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
base, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-05 14:38:02 +08:00
if !quote.Equals(solana.WrappedSol) {
2025-12-26 10:57:37 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
var args photonSwapPumpAmmArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[8:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse swap pump amm tokens args: %w", err)
}
if args.FromAmount > args.ToAmount {
// sell; ignore
return nil, nil
}
solAmount := args.FromAmount * (100000000 - 1234568) / 100000000
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "photon",
2025-12-26 10:57:37 +08:00
Maker: buyer.String(),
Token0Address: base.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.ToAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "PumpAMM",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: args.ToAmount,
Token1AmountUint64: solAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parsePumpAmmInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if matchMethod(data, pumpAmmBuyTokensIX) || matchMethod(data, pumpAmmBuyTokensV2IX) {
return parsePumpAmmBuy(tx, accounts, data)
} else if matchMethod(data, pumpAmmSellTokensIX) {
return parsePumpAmmSell(tx, accounts, data)
2025-12-30 11:03:11 +08:00
}
return nil, nil
}
2026-01-23 17:58:59 +08:00
func parseTermInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2025-12-30 11:03:11 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if len(data) < 24 {
2025-12-26 10:57:37 +08:00
return nil, nil
}
2025-12-30 11:03:11 +08:00
switch {
2026-01-23 17:58:59 +08:00
case bytes.Equal(data[:8], terminalBuyTokensIX):
return parseTermBuy(tx, accounts, data)
case bytes.Equal(data[:8], terminalSellTokensIX):
return parseTermSell(tx, accounts, data)
case bytes.Equal(data[:8], terminalAmmSellTokensIX):
return parseTermAmmSell(tx, accounts, data)
2025-12-30 11:03:11 +08:00
default:
return nil, nil
}
}
2026-01-23 17:58:59 +08:00
func parseTermAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
solAmount := binary.LittleEndian.Uint64(data[8:16])
tokenAmount := binary.LittleEndian.Uint64(data[16:24])
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "term",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseTermBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2]) // getStaticKey(staticKeys, int(instruction.Accounts[2]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
solAmount := binary.LittleEndian.Uint64(data[8:16])
tokenAmount := binary.LittleEndian.Uint64(data[16:24])
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "term",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-08 12:44:47 +08:00
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseTermSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
tokenAmount := binary.LittleEndian.Uint64(data[8:16])
solAmount := binary.LittleEndian.Uint64(data[16:24])
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "term",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-08 12:44:47 +08:00
ExactSOL: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
2025-12-26 10:57:37 +08:00
}
func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 {
2026-01-05 14:38:02 +08:00
return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
var args pumpAmmBuyArgs
if err := borsh.Deserialize(&args, data[8:]); err == nil {
return args.Amount, args.MaxSolCost, nil
}
if len(data) >= 24 {
amount := binary.LittleEndian.Uint64(data[8:16])
maxSol := binary.LittleEndian.Uint64(data[16:24])
return amount, maxSol, nil
}
return 0, 0, fmt.Errorf("failed to parse buy tokens args")
}
2026-01-23 17:58:59 +08:00
func parsePumpAmmBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
amount, maxSol, err := decodePumpAmmBuyArgs(data)
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
exactIn := false
2026-01-23 17:58:59 +08:00
if matchMethod(data, pumpAmmBuyTokensV2IX) {
2025-12-30 11:03:11 +08:00
temp := amount
amount = maxSol
maxSol = temp
exactIn = true
}
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(accounts) < 7 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
base, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-05 14:38:02 +08:00
if !quote.Equals(solana.WrappedSol) {
2025-12-26 10:57:37 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "pumpamm",
2025-12-26 10:57:37 +08:00
Maker: buyer.String(),
Token0Address: base.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(maxSol),
Program: "PumpAMM",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2025-12-30 11:03:11 +08:00
ExactSOL: exactIn,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: amount,
Token1AmountUint64: maxSol,
}, nil
}
2026-01-23 17:58:59 +08:00
func parsePumpAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
amount, minSol, err := decodePumpAmmBuyArgs(data)
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
if len(accounts) < 7 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
base, err := tx.GetAccount(accounts[3]) // getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-05 14:38:02 +08:00
if !quote.Equals(solana.WrappedSol) {
2025-12-26 10:57:37 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "pumpamm",
2025-12-26 10:57:37 +08:00
Maker: buyer.String(),
Token0Address: base.String(),
Token1Address: wsolMint,
2025-12-30 11:03:11 +08:00
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(minSol),
2025-12-26 10:57:37 +08:00
Program: "PumpAMM",
2025-12-30 11:03:11 +08:00
Event: "sell",
2025-12-26 10:57:37 +08:00
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: amount,
Token1AmountUint64: minSol,
2025-12-26 10:57:37 +08:00
}, nil
}
2026-01-23 17:58:59 +08:00
func parseBoboInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if len(data) < 8 || !bytes.Equal(data[:8], boboBuyPumpTokensIX) {
2025-12-26 10:57:37 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
if len(accounts) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
if len(data) < 16 {
return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
var args boboBuyArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[8:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "bobo",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
2025-12-26 10:57:37 +08:00
Token1Address: wsolMint,
2025-12-30 11:03:11 +08:00
Token0Amount: decimal.NewFromInt(1),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
2025-12-26 10:57:37 +08:00
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: 1,
Token1AmountUint64: args.SolAmount,
2025-12-26 10:57:37 +08:00
}, nil
}
2026-01-23 17:58:59 +08:00
func parseQtkvInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2025-12-30 11:03:11 +08:00
2026-01-23 17:58:59 +08:00
if matchMethod(data, qtkvBuyTokensIX) {
return parseQtkvBuy(tx, accounts, data)
} else if matchMethod(data, qtkvAmmSellTokensIX) {
return parseQtkvAmmSell(tx, accounts, data)
} else if matchMethod(data, qtkvSellTokensIX) {
return parseQtkvSell(tx, accounts, data)
2025-12-26 10:57:37 +08:00
}
2025-12-30 11:03:11 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
func parseQtkvSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 11 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
if len(data) < 24 {
return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[10]) //getStaticKey(staticKeys, int(instruction.Accounts[10]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2025-12-30 11:03:11 +08:00
// in sell, sol amount is not directly provided, so we set it to 0
2026-01-23 17:58:59 +08:00
tokenAmount := binary.LittleEndian.Uint64(data[19:25])
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "qtkv",
2025-12-26 10:57:37 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
2025-12-30 11:03:11 +08:00
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: decimal.Zero,
2025-12-26 10:57:37 +08:00
Program: "Pump",
2025-12-30 11:03:11 +08:00
Event: "sell",
2025-12-26 10:57:37 +08:00
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: tokenAmount,
Token1AmountUint64: 0,
2025-12-26 10:57:37 +08:00
}, nil
}
2026-01-23 17:58:59 +08:00
func parseQtkvAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 11 {
2025-12-30 11:03:11 +08:00
return nil, fmt.Errorf("accounts too short")
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
if len(data) < 24 {
return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[10]) //getStaticKey(staticKeys, int(instruction.Accounts[10]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
2025-12-30 11:03:11 +08:00
if err != nil {
return nil, err
2025-12-26 10:57:37 +08:00
}
2025-12-30 11:03:11 +08:00
// in sell, sol amount is not directly provided, so we set it to 0
2026-01-23 17:58:59 +08:00
tokenAmount := binary.LittleEndian.Uint64(data[19:25])
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "qtkv",
2025-12-30 11:03:11 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: decimal.Zero,
Program: "PumpAMM",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-30 11:03:11 +08:00
Token0AmountUint64: tokenAmount,
Token1AmountUint64: 0,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseQtkvBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[0]) // getStaticKey(staticKeys, int(instruction.Accounts[0]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
var args qtkvBuyArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[1:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "qtkv",
2025-12-26 10:57:37 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenNumber),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: args.TokenNumber,
Token1AmountUint64: args.SolAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseFjszInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2025-12-26 10:57:37 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if !matchMethod(data, fjszBuyTokensIX) {
2025-12-26 10:57:37 +08:00
return nil, nil
}
2026-01-23 17:58:59 +08:00
if len(accounts) < 7 {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
if len(data) < 16 {
return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(data))
2025-12-26 10:57:37 +08:00
}
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
2025-12-26 10:57:37 +08:00
if err != nil {
return nil, err
}
var args fjszBuyArgs
2026-01-23 17:58:59 +08:00
if err := borsh.Deserialize(&args, data[8:]); err != nil {
2025-12-26 10:57:37 +08:00
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
2025-12-30 11:03:11 +08:00
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-06 16:42:07 +08:00
Label: "fjsz",
2025-12-26 10:57:37 +08:00
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2025-12-26 10:57:37 +08:00
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: args.SolAmount,
}, nil
}
2026-01-23 17:58:59 +08:00
func parseBonkInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
2026-01-08 16:07:57 +08:00
2026-01-23 17:58:59 +08:00
if len(data) == 0 {
2026-01-08 16:07:57 +08:00
return nil, fmt.Errorf("data is empty")
}
2026-01-23 17:58:59 +08:00
if matchMethod(data, bonkBuyAndSellTokensIX) {
return parseBonkBuyAndSell(tx, accounts, data)
2026-01-08 16:07:57 +08:00
}
return nil, nil
}
2026-01-23 17:58:59 +08:00
func parseBonkBuyAndSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
if len(accounts) < 8 {
2026-01-08 16:07:57 +08:00
return nil, fmt.Errorf("accounts too short")
}
2026-01-23 17:58:59 +08:00
programId, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
2026-01-08 16:07:57 +08:00
if err != nil {
return nil, err
}
if programId != pumpProgramID {
return nil, nil
}
2026-01-23 17:58:59 +08:00
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
2026-01-08 16:07:57 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
flagAccount, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
2026-01-08 16:07:57 +08:00
if err != nil {
return nil, err
}
2026-01-23 17:58:59 +08:00
amount1 := binary.LittleEndian.Uint64(data[17:25])
amount2 := binary.LittleEndian.Uint64(data[25:33])
2026-01-08 16:07:57 +08:00
if user == flagAccount {
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6]))
2026-01-08 16:07:57 +08:00
if err != nil {
return nil, err
}
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-08 16:07:57 +08:00
Label: "bonk",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount2),
Token1Amount: formatSolAmount(amount1),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2026-01-08 16:07:57 +08:00
Token0AmountUint64: amount2,
Token1AmountUint64: amount1,
}, nil
} else {
2026-01-23 17:58:59 +08:00
mint, err := tx.GetAccount(accounts[5]) //getStaticKey(staticKeys, int(instruction.Accounts[5]))
2026-01-08 16:07:57 +08:00
if err != nil {
return nil, err
}
return &TxSignal{
2026-01-23 17:58:59 +08:00
TxHash: tx.Signatures(),
2026-01-08 16:07:57 +08:00
Label: "bonk",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount1),
Token1Amount: formatSolAmount(amount2),
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
2026-01-23 17:58:59 +08:00
Block: tx.Block(),
2026-01-08 16:07:57 +08:00
Token0AmountUint64: amount1,
Token1AmountUint64: amount2,
}, nil
}
}
2025-12-30 11:03:11 +08:00
func matchMethod(data []byte, methods []byte) bool {
if len(data) < len(methods) {
return false
}
return bytes.Equal(data[0:len(methods)], methods)
}