1902 lines
54 KiB
Go
1902 lines
54 KiB
Go
package shreder
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math/big"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gagliardetto/solana-go"
|
|
"github.com/mr-tron/base58"
|
|
"github.com/near/borsh-go"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
const (
|
|
wsolMint = "So11111111111111111111111111111111111111112"
|
|
tokenProgram = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
)
|
|
|
|
// program ids
|
|
var (
|
|
pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
|
// has no sell function with pump and pump.amm program
|
|
azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB")
|
|
|
|
// only buy function with pump program
|
|
f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq")
|
|
// only pump.fun function
|
|
photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW")
|
|
|
|
pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
|
|
|
boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM")
|
|
|
|
qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7")
|
|
|
|
// only buy function with pump program
|
|
fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK")
|
|
|
|
flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
|
|
|
|
terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
|
|
|
|
jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
|
|
|
|
gmgnProgramID = solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb")
|
|
|
|
bonkProgramID = solana.MustPublicKeyFromBase58("BBRouter1cVunVXvkcqeKkZQcBK7ruan37PPm3xzWaXD")
|
|
)
|
|
|
|
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)
|
|
}
|
|
|
|
// instruction discriminators
|
|
var (
|
|
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}
|
|
|
|
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}
|
|
|
|
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}
|
|
|
|
bonkBuyAndSellTokensIX = []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}
|
|
)
|
|
|
|
type addressTableLookup struct {
|
|
AccountKeyOffset int
|
|
WriteOffset int
|
|
WriteLen int
|
|
ReadOffset int
|
|
ReadLen int
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
type versionedTransaction struct {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
type flasArgs struct {
|
|
Amount1 uint64
|
|
Amount2 uint64
|
|
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
|
|
}
|
|
|
|
var (
|
|
versionedPool = sync.Pool{}
|
|
)
|
|
|
|
type compiledInstruction struct {
|
|
ProgramIDIndex uint8
|
|
AccountsLen int
|
|
AccountsOffset int
|
|
DataOffset int
|
|
DataLen int
|
|
}
|
|
|
|
func requireVersionedPool() *versionedTransaction {
|
|
v := versionedPool.Get()
|
|
if v == nil {
|
|
return &versionedTransaction{
|
|
TableLookups: make([]solana.PublicKey, 0, 16),
|
|
Instrs: make([]compiledInstruction, 0, 16),
|
|
ATL: make([]addressTableLookup, 0, 4),
|
|
}
|
|
}
|
|
return v.(*versionedTransaction)
|
|
}
|
|
|
|
func releaseVersionedPool(v *versionedTransaction) {
|
|
if v == nil {
|
|
return
|
|
}
|
|
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()
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// ParseTransaction mirrors the Rust parse_transaction entry point.
|
|
func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables, txCh chan<- TxSignal, stats bool) {
|
|
var stat Stats
|
|
if stats {
|
|
stat.Start = time.Now()
|
|
}
|
|
versioned, err := toVersionedTransaction(update)
|
|
if err != nil || versioned == nil || versioned.signatures == 0 {
|
|
return
|
|
}
|
|
defer func() {
|
|
releaseVersionedPool(versioned)
|
|
}()
|
|
parseTransaction(stat, versioned, loader, txCh)
|
|
}
|
|
|
|
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 {
|
|
lookupTableOk := true
|
|
for i := 0; i < versioned.addressTableLookups; i++ {
|
|
lookups, idxs, _, err := versioned.GetAddressTableLookups(i)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if len(idxs) == 0 {
|
|
continue
|
|
}
|
|
lookupTableOk = loader.FillToTx(versioned, lookups, idxs)
|
|
if !lookupTableOk {
|
|
break
|
|
}
|
|
}
|
|
if lookupTableOk {
|
|
for i := 0; i < versioned.addressTableLookups; i++ {
|
|
|
|
lookups, _, idxs, err := versioned.GetAddressTableLookups(i)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if len(idxs) == 0 {
|
|
continue
|
|
}
|
|
lookupTableOk = loader.FillToTx(versioned, lookups, idxs)
|
|
if !lookupTableOk {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
// versioned.Message.StaticAccountKeys = staticKeys
|
|
}
|
|
|
|
//instructions := versioned.GetInstruction()
|
|
|
|
for i := 0; i < versioned.GetInstructionLen(); i++ {
|
|
programID, accounts, data, err := versioned.GetInstruction(i)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
handler, ok := parsedMap[programID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
txRes, err := handler.Func(versioned, accounts, data)
|
|
appendParsed(stat, parsed, txRes, err, versioned, handler.Label)
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func appendParsed(stat Stats, txCh chan<- TxSignal, parsed *TxSignal, err error, tx TransactionGetter, label string) {
|
|
if err != nil {
|
|
if !strings.HasPrefix(err.Error(), "account index") {
|
|
logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", tx.Signatures())
|
|
}
|
|
return
|
|
}
|
|
if parsed != nil {
|
|
parsed.Label = label
|
|
if !stat.Start.IsZero() {
|
|
stat.Done = time.Now()
|
|
parsed.Stats = stat
|
|
}
|
|
txCh <- *parsed
|
|
} else {
|
|
// logger.Debug("txparser: no parsed result", "label", label, "tx_hash", tx.Signatures())
|
|
}
|
|
return
|
|
}
|
|
|
|
func toVersionedTransaction(update *SubscribeUpdateTransaction) (*versionedTransaction, error) {
|
|
if update == nil || update.Transaction == nil || update.Transaction.Message == nil {
|
|
return nil, fmt.Errorf("transaction is nil")
|
|
}
|
|
protoTx := update.Transaction
|
|
msg := protoTx.Message
|
|
versioned := requireVersionedPool()
|
|
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()
|
|
return versioned, nil
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
func parsePumpInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(data) < 8 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
|
|
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)
|
|
}
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
func parsePumpCreate(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
creator, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "pump",
|
|
Maker: creator.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: decimal.Zero,
|
|
Token1Amount: decimal.Zero,
|
|
Program: "Pump",
|
|
Event: "create",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: 0,
|
|
Token1AmountUint64: 0,
|
|
}, nil
|
|
}
|
|
|
|
func parsePumpCreateV2(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 8 {
|
|
return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(data))
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tokenProgramKey, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args pumpCreateCoinV2Args
|
|
if err := borsh.Deserialize(&args, data[8:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse create coin v2 args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "pump",
|
|
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,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: 0,
|
|
Token1AmountUint64: 0,
|
|
}, nil
|
|
}
|
|
|
|
func decodePumpBuyArgs(data []byte) (uint64, uint64, error) {
|
|
if len(data) < 9 {
|
|
return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data))
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
func parsePumpBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
amount, sol, err := decodePumpBuyArgs(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exactIn := false
|
|
if matchMethod(data, pumpBuyV2TokensIX) {
|
|
temp := amount
|
|
amount = sol
|
|
sol = temp
|
|
exactIn = true
|
|
}
|
|
|
|
if len(accounts) < 7 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[2])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
buyer, err := tx.GetAccount(accounts[6])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "pump",
|
|
Maker: buyer.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(amount),
|
|
Token1Amount: formatSolAmount(sol),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
ExactSOL: exactIn,
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: amount,
|
|
Token1AmountUint64: sol,
|
|
}, nil
|
|
}
|
|
|
|
func decodePumpSellArgs(data []byte) (uint64, uint64, error) {
|
|
if len(data) < 9 {
|
|
return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data))
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
func parsePumpSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
amount, minSol, err := decodePumpSellArgs(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(accounts) < 7 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[2])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
seller, err := tx.GetAccount(accounts[6])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "pump",
|
|
Maker: seller.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(amount),
|
|
Token1Amount: formatSolAmount(minSol),
|
|
Program: "Pump",
|
|
Event: "sell",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: amount,
|
|
Token1AmountUint64: minSol,
|
|
}, nil
|
|
}
|
|
|
|
func parseAzczInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
|
|
if matchMethod(data, azczBuyTokensIX) {
|
|
return parseAzczBuy(tx, accounts, data)
|
|
} else if matchMethod(data, azczAmmBuyTokensIX) {
|
|
return parseAzczAmmBuy(tx, accounts, data)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func parseAzczAmmBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[3]) // getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(data) < 17 {
|
|
return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(data))
|
|
}
|
|
|
|
solAmount := binary.LittleEndian.Uint64(data[1:9])
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "azcz",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: decimal.Zero,
|
|
Token1Amount: formatSolAmount(solAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
ExactSOL: true,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: 0,
|
|
Token1AmountUint64: solAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseAzczBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(data) < 2 {
|
|
return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(data))
|
|
}
|
|
|
|
var args azczBuyArgs
|
|
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "azcz",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(args.TokenAmount),
|
|
Token1Amount: formatSolAmount(args.SolAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: args.TokenAmount,
|
|
Token1AmountUint64: args.SolAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseF5tfInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
if !matchMethod(data, f5tfBuyTokensIX) {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(accounts) < 7 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(data) < 2 {
|
|
return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(data))
|
|
}
|
|
|
|
var args f5tfBuyArgs
|
|
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "f5tf",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(args.TokenAmount),
|
|
Token1Amount: formatSolAmount(args.SolAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: args.TokenAmount,
|
|
Token1AmountUint64: args.SolAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseFlasInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
if len(data) == 10 && data[0] == 1 {
|
|
return nil, nil
|
|
}
|
|
if len(data) < 20 {
|
|
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(data))
|
|
}
|
|
methodData := data[17:20]
|
|
//if !matchMethod(methodData, flasBuyTokensIX) {
|
|
// return nil, nil
|
|
//}
|
|
if matchMethod(methodData, flasBuyTokensIX) {
|
|
return parseFlasBuy(tx, accounts, data)
|
|
} else if matchMethod(methodData, flasSellTokensIX) {
|
|
return parseFlasSell(tx, accounts, data)
|
|
} else if matchMethod(methodData, flasAmmBuyTokensIX) {
|
|
return parseFlasAmmBuy(tx, accounts, data)
|
|
} else if matchMethod(methodData, flasAmmSellTokensIX) {
|
|
return parseFlasAmmSell(tx, accounts, data)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func parseFlasAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 10 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[9]) //getStaticKey(staticKeys, int(instruction.Accounts[9]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[1]) // getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
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 TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 10 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[9]) //getStaticKey(staticKeys, int(instruction.Accounts[9]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[1]) // getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
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 TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 9 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[8]) //getStaticKey(staticKeys, int(instruction.Accounts[8]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
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 TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 9 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[8]) //getStaticKey(staticKeys, int(instruction.Accounts[8]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(data) > 20 {
|
|
data = data[:20]
|
|
}
|
|
var args flasArgs
|
|
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
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 parseGMGNInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
if len(data) < 8 {
|
|
return nil, nil
|
|
}
|
|
|
|
if matchMethod(data, gmgnBuyTokensIX) {
|
|
return parseGMGNBuy(tx, accounts, data)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func parseGMGNBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 24 {
|
|
return nil, fmt.Errorf("data too short for gmgn buy args, len=%d", len(data))
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
solAmount := binary.LittleEndian.Uint64(data[8:16])
|
|
tokenAmount := binary.LittleEndian.Uint64(data[16:24])
|
|
|
|
return &TxSignal{
|
|
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,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: tokenAmount,
|
|
Token1AmountUint64: solAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parsePhotonInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
if len(data) < 8 {
|
|
return nil, nil
|
|
}
|
|
|
|
switch {
|
|
case bytes.Equal(data[:8], photonBuyPumpTokensIX):
|
|
return parsePhotonBuy(tx, accounts, data)
|
|
case bytes.Equal(data[:8], photonSwapPumpAmmIX):
|
|
return parsePhotonSwap(tx, accounts, data)
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func parsePhotonBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 16 {
|
|
return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(data))
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args photonBuyPumpArgs
|
|
if err := borsh.Deserialize(&args, data[8:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
solAmount := args.SolAmount * (100000000 - 1234568) / 100000000
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "photon",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(args.TokenAmount),
|
|
Token1Amount: formatSolAmount(solAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
ExactSOL: true,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: args.TokenAmount,
|
|
Token1AmountUint64: solAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parsePhotonSwap(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 16 {
|
|
return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(data))
|
|
}
|
|
|
|
base, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !quote.Equals(solana.WrappedSol) {
|
|
return nil, nil
|
|
}
|
|
|
|
buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args photonSwapPumpAmmArgs
|
|
if err := borsh.Deserialize(&args, data[8:]); err != nil {
|
|
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
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "photon",
|
|
Maker: buyer.String(),
|
|
Token0Address: base.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(args.ToAmount),
|
|
Token1Amount: formatSolAmount(solAmount),
|
|
Program: "PumpAMM",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: args.ToAmount,
|
|
Token1AmountUint64: solAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parsePumpAmmInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
|
|
if matchMethod(data, pumpAmmBuyTokensIX) || matchMethod(data, pumpAmmBuyTokensV2IX) {
|
|
return parsePumpAmmBuy(tx, accounts, data)
|
|
} else if matchMethod(data, pumpAmmSellTokensIX) {
|
|
return parsePumpAmmSell(tx, accounts, data)
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
func parseTermInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
if len(data) < 24 {
|
|
return nil, nil
|
|
}
|
|
|
|
switch {
|
|
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)
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func parseTermAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
solAmount := binary.LittleEndian.Uint64(data[8:16])
|
|
tokenAmount := binary.LittleEndian.Uint64(data[16:24])
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "term",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(tokenAmount),
|
|
Token1Amount: formatSolAmount(solAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
ExactSOL: true,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: tokenAmount,
|
|
Token1AmountUint64: solAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseTermBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
mint, err := tx.GetAccount(accounts[2]) // getStaticKey(staticKeys, int(instruction.Accounts[2]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
solAmount := binary.LittleEndian.Uint64(data[8:16])
|
|
tokenAmount := binary.LittleEndian.Uint64(data[16:24])
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "term",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(tokenAmount),
|
|
Token1Amount: formatSolAmount(solAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
ExactSOL: true,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: tokenAmount,
|
|
Token1AmountUint64: solAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseTermSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tokenAmount := binary.LittleEndian.Uint64(data[8:16])
|
|
solAmount := binary.LittleEndian.Uint64(data[16:24])
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "term",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(tokenAmount),
|
|
Token1Amount: formatSolAmount(solAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
ExactSOL: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: tokenAmount,
|
|
Token1AmountUint64: solAmount,
|
|
}, nil
|
|
}
|
|
|
|
func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) {
|
|
if len(data) < 9 {
|
|
return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data))
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
func parsePumpAmmBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
amount, maxSol, err := decodePumpAmmBuyArgs(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exactIn := false
|
|
if matchMethod(data, pumpAmmBuyTokensV2IX) {
|
|
temp := amount
|
|
amount = maxSol
|
|
maxSol = temp
|
|
exactIn = true
|
|
}
|
|
|
|
if len(accounts) < 7 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
base, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !quote.Equals(solana.WrappedSol) {
|
|
return nil, nil
|
|
}
|
|
|
|
buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "pumpamm",
|
|
Maker: buyer.String(),
|
|
Token0Address: base.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(amount),
|
|
Token1Amount: formatSolAmount(maxSol),
|
|
Program: "PumpAMM",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
ExactSOL: exactIn,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: amount,
|
|
Token1AmountUint64: maxSol,
|
|
}, nil
|
|
}
|
|
|
|
func parsePumpAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
amount, minSol, err := decodePumpAmmBuyArgs(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(accounts) < 7 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
base, err := tx.GetAccount(accounts[3]) // getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !quote.Equals(solana.WrappedSol) {
|
|
return nil, nil
|
|
}
|
|
|
|
buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "pumpamm",
|
|
Maker: buyer.String(),
|
|
Token0Address: base.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(amount),
|
|
Token1Amount: formatSolAmount(minSol),
|
|
Program: "PumpAMM",
|
|
Event: "sell",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: amount,
|
|
Token1AmountUint64: minSol,
|
|
}, nil
|
|
}
|
|
|
|
func parseBoboInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
if len(data) < 8 || !bytes.Equal(data[:8], boboBuyPumpTokensIX) {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 16 {
|
|
return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(data))
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args boboBuyArgs
|
|
if err := borsh.Deserialize(&args, data[8:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "bobo",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: decimal.NewFromInt(1),
|
|
Token1Amount: formatSolAmount(args.SolAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: 1,
|
|
Token1AmountUint64: args.SolAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseQtkvInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func parseQtkvSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 11 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 24 {
|
|
return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(data))
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[10]) //getStaticKey(staticKeys, int(instruction.Accounts[10]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// in sell, sol amount is not directly provided, so we set it to 0
|
|
tokenAmount := binary.LittleEndian.Uint64(data[19:25])
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "qtkv",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(tokenAmount),
|
|
Token1Amount: decimal.Zero,
|
|
Program: "Pump",
|
|
Event: "sell",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: tokenAmount,
|
|
Token1AmountUint64: 0,
|
|
}, nil
|
|
}
|
|
|
|
func parseQtkvAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 11 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 24 {
|
|
return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(data))
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[10]) //getStaticKey(staticKeys, int(instruction.Accounts[10]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// in sell, sol amount is not directly provided, so we set it to 0
|
|
tokenAmount := binary.LittleEndian.Uint64(data[19:25])
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "qtkv",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(tokenAmount),
|
|
Token1Amount: decimal.Zero,
|
|
Program: "PumpAMM",
|
|
Event: "sell",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: tokenAmount,
|
|
Token1AmountUint64: 0,
|
|
}, nil
|
|
}
|
|
|
|
func parseQtkvBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[0]) // getStaticKey(staticKeys, int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args qtkvBuyArgs
|
|
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "qtkv",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(args.TokenNumber),
|
|
Token1Amount: formatSolAmount(args.SolAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: args.TokenNumber,
|
|
Token1AmountUint64: args.SolAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseFjszInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
|
|
if !matchMethod(data, fjszBuyTokensIX) {
|
|
return nil, nil
|
|
}
|
|
if len(accounts) < 7 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
if len(data) < 16 {
|
|
return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(data))
|
|
}
|
|
|
|
mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var args fjszBuyArgs
|
|
if err := borsh.Deserialize(&args, data[8:]); err != nil {
|
|
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
Label: "fjsz",
|
|
Maker: user.String(),
|
|
Token0Address: mint.String(),
|
|
Token1Address: wsolMint,
|
|
Token0Amount: formatTokenAmount(args.TokenAmount),
|
|
Token1Amount: formatSolAmount(args.SolAmount),
|
|
Program: "Pump",
|
|
Event: "buy",
|
|
IsToken2022: false,
|
|
IsMayhemMode: false,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: args.TokenAmount,
|
|
Token1AmountUint64: args.SolAmount,
|
|
}, nil
|
|
}
|
|
|
|
func parseBonkInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("data is empty")
|
|
}
|
|
|
|
if matchMethod(data, bonkBuyAndSellTokensIX) {
|
|
return parseBonkBuyAndSell(tx, accounts, data)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func parseBonkBuyAndSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
|
|
if len(accounts) < 8 {
|
|
return nil, fmt.Errorf("accounts too short")
|
|
}
|
|
programId, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if programId != pumpProgramID {
|
|
return nil, nil
|
|
}
|
|
|
|
user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
flagAccount, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
amount1 := binary.LittleEndian.Uint64(data[17:25])
|
|
amount2 := binary.LittleEndian.Uint64(data[25:33])
|
|
|
|
if user == flagAccount {
|
|
mint, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
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,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: amount2,
|
|
Token1AmountUint64: amount1,
|
|
}, nil
|
|
} else {
|
|
mint, err := tx.GetAccount(accounts[5]) //getStaticKey(staticKeys, int(instruction.Accounts[5]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &TxSignal{
|
|
TxHash: tx.Signatures(),
|
|
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,
|
|
Block: tx.Block(),
|
|
Token0AmountUint64: amount1,
|
|
Token1AmountUint64: amount2,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func matchMethod(data []byte, methods []byte) bool {
|
|
if len(data) < len(methods) {
|
|
return false
|
|
}
|
|
return bytes.Equal(data[0:len(methods)], methods)
|
|
}
|