From 9f73e8f57f885c2c9e372557a81ce576130f5a48 Mon Sep 17 00:00:00 2001 From: thloyi Date: Fri, 23 Jan 2026 17:58:59 +0800 Subject: [PATCH] pipo tx parse --- cmd/debug_jupv6/main.go | 62 -- cmd/shreder/main.go | 23 +- pkg/shreder/addresstables.go | 6 +- pkg/shreder/client.go | 57 +- pkg/shreder/dflow.go | 31 +- pkg/shreder/entry.go | 334 ++++++++++ pkg/shreder/entry_test.go | 41 ++ pkg/shreder/juptierv6.go | 69 +- pkg/shreder/okxonchainlab.go | 38 +- pkg/shreder/tx.go | 3 +- pkg/shreder/txparser.go | 1182 ++++++++++++++++++---------------- pkg/shreder/txparser_test.go | 72 +-- 12 files changed, 1172 insertions(+), 746 deletions(-) delete mode 100644 cmd/debug_jupv6/main.go create mode 100644 pkg/shreder/entry.go create mode 100644 pkg/shreder/entry_test.go diff --git a/cmd/debug_jupv6/main.go b/cmd/debug_jupv6/main.go deleted file mode 100644 index b5544fa..0000000 --- a/cmd/debug_jupv6/main.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "encoding/hex" - "fmt" - "os" -) - -func main() { - hexData := "bb64facc31c4af14be34e6edcc0000006f03a4df67000000b903320000000300000064342100024b00000000dc0500026310270203" - b, err := hex.DecodeString(hexData) - if err != nil { - panic(err) - } - payload := b[8:] - - off := 0 - read := func(n int) []byte { - if off+n > len(payload) { - fmt.Printf("OOB read: off=%d n=%d len=%d\n", off, n, len(payload)) - os.Exit(1) - } - out := payload[off : off+n] - off += n - return out - } - u8 := func() uint8 { return read(1)[0] } - leU16 := func() uint16 { - b := read(2) - return uint16(b[0]) | uint16(b[1])<<8 - } - leU32 := func() uint32 { - b := read(4) - return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 - } - leU64 := func() uint64 { - b := read(8) - return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | - uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 - } - - fmt.Printf("payload len=%d\n", len(payload)) - amountIn := leU64() - quotedOut := leU64() - slippage := leU16() - platform := leU16() - posSlip := leU16() - fmt.Printf("in=%d out=%d slip=%d plat=%d pos=%d\n", amountIn, quotedOut, slippage, platform, posSlip) - - planLen := leU32() - fmt.Printf("planLen=%d\n", planLen) - for i := uint32(0); i < planLen; i++ { - swapTag := u8() - fmt.Printf("step[%d] swapTag=%d (0x%02x) off=%d\n", i, swapTag, swapTag, off) - // payload depends on swapTag; we don't know, so just print next few bytes and stop - bps := leU16() - inIdx := u8() - outIdx := u8() - fmt.Printf(" bps=%d inIdx=%d outIdx=%d off=%d\n", bps, inIdx, outIdx, off) - } - fmt.Printf("done off=%d\n", off) -} diff --git a/cmd/shreder/main.go b/cmd/shreder/main.go index 04623ed..8e1f52c 100644 --- a/cmd/shreder/main.go +++ b/cmd/shreder/main.go @@ -8,8 +8,10 @@ import ( "os" "os/signal" "syscall" + "time" "github.com/gagliardetto/solana-go/rpc" + "github.com/shopspring/decimal" "github.com/samlior/libsam/pkg/shreder" ) @@ -76,9 +78,9 @@ func main() { cancel() }() // async read from shreder - txCh := make(chan shreder.TxSignalBatch, 1000) + txCh := make(chan shreder.TxSignal, 1000) go func() { - err := shrederClient.ReadSync(ctx, txCh) + err := shrederClient.ReadEntriesSync(ctx, txCh) if err != nil { if !errors.Is(err, context.Canceled) { panic(err) @@ -90,13 +92,20 @@ func main() { select { case <-ctx.Done(): return - case txBatch := <-txCh: + case tx := <-txCh: //jsonData, _ := json.MarshalIndent(txBatch, "", " ") - for _, tx := range txBatch { - if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" { - fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart)) - } + if tx.Token0Amount.GreaterThan(decimal.NewFromInt(100)) && (tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow") { + fmt.Println(time.Now(), "===============", tx.TxHash, + "parse time:", tx.Stats.Done.Sub(tx.Stats.Filter), + "decode time:", tx.Stats.Decoded.Sub(tx.Stats.FEC), + "filter time:", tx.Stats.Filter.Sub(tx.Stats.Decoded), + "dataLen", tx.Stats.DataLen, "txCount", tx.Stats.TxCount, "txOffset", tx.Stats.TxOffset, tx.Label, tx.Event, "token:", tx.Token0Amount) } + + //if tx.Token0Amount.GreaterThan(decimal.NewFromInt(100)) && (tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow") { + // fmt.Println(time.Now(), "===============", tx.TxHash, + // tx.Label, tx.Event, "token:", tx.Token0Amount) + //} //fmt.Println(txBatch[0].TxHash) } } diff --git a/pkg/shreder/addresstables.go b/pkg/shreder/addresstables.go index 3c42b77..c09e063 100644 --- a/pkg/shreder/addresstables.go +++ b/pkg/shreder/addresstables.go @@ -59,7 +59,6 @@ func (at *AddressTables) loadAddressTable(tablePubkey solana.PublicKey) ([]solan addresses = append(addresses, solana.PublicKeyFromBytes(data[offset:offset+32])) offset += 32 } - // addresses = append(addresses, solana.PublicKeyFromBytes(data[start:start+32])) return addresses, nil } @@ -93,10 +92,11 @@ func (at *AddressTables) load(tablePubkey solana.PublicKey) { total := at.tables.Len() logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total) } + }) } -func (at *AddressTables) FillToTx(tx *versionedTransaction, tablePubkey solana.PublicKey, idx []uint8) bool { +func (at *AddressTables) FillToTx(tx FillableTransaction, tablePubkey solana.PublicKey, idx []uint8) bool { addresses, ok := at.tables.Get(tablePubkey) if !ok { at.load(tablePubkey) @@ -112,7 +112,7 @@ func (at *AddressTables) FillToTx(tx *versionedTransaction, tablePubkey solana.P } return false } - tx.Message.StaticAccountKeys = append(tx.Message.StaticAccountKeys, addresses.addresses[i]) + tx.FillLookupTable(addresses.addresses[i]) } return true } diff --git a/pkg/shreder/client.go b/pkg/shreder/client.go index d4fcb80..ade2e2b 100644 --- a/pkg/shreder/client.go +++ b/pkg/shreder/client.go @@ -70,7 +70,7 @@ func NewShrederClient( poolSize := runtime.NumCPU()*2 + 2 logger.Info("creating shreder client", "url", url, "pool_size", poolSize) - pool, err := ants.NewPool(poolSize, ants.WithNonblocking(false)) + pool, err := ants.NewPool(poolSize, ants.WithNonblocking(true)) if err != nil { return nil, func() {}, err } @@ -113,7 +113,42 @@ func (c *Client) Wait() { logger.Debug("shreder client stopped") } -func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error { +func (c *Client) ReadEntriesSync(ctx context.Context, txCh chan<- TxSignal) error { + stream, err := c.client.SubscribeEntries(ctx, &SubscribeEntriesRequest{}) + if err != nil { + return err + } + + logger.Debug("reading entries from shreder client") + for { + response, err := stream.Recv() + if err != nil { + return err + } + slot := response.Slot + if c.enableBlockStats { + now := time.Now() + if c.lastSlotTime.IsZero() || slot > c.lastSlot { + if !c.lastSlotTime.IsZero() { + logger.Info("block processed", "running", c.pool.Running(), "slot", slot, "prev_slot", c.lastSlot, "delta_ms", now.Sub(c.lastSlotTime).Milliseconds()) + } + c.lastSlot = slot + c.lastSlotTime = now + } + } + + entries := response.Entries + + err = c.pool.Submit(func() { + ParseEntries(slot, entries, c.tableLoader, txCh, c.enableParseStats) + }) + if err != nil { + return err + } + } +} + +func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error { stream, err := c.client.SubscribeTransactions(ctx) if err != nil { return err @@ -149,23 +184,11 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error txData := response.Transaction err = c.pool.Submit(func() { - txBatch := ParseTransaction(txData, c.tableLoader, c.enableParseStats) - if len(txBatch) == 0 { - return - } - - for _, tx := range txBatch { - tx.Source = "shreder" - } - - select { - case <-ctx.Done(): - return - case txCh <- txBatch: - } + ParseTransaction(txData, c.tableLoader, txCh, c.enableParseStats) }) if err != nil { - return err + logger.Error("failed to submit transaction: ", "err", err) } + } } diff --git a/pkg/shreder/dflow.go b/pkg/shreder/dflow.go index 7e39295..3ab1370 100644 --- a/pkg/shreder/dflow.go +++ b/pkg/shreder/dflow.go @@ -232,19 +232,14 @@ func decodeSwap2Params(data []byte) (*dflowSwapParams, error) { return out, nil } -func parseDFlowInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } - ix := msg.Instructions[instructionIndex] - if len(ix.Data) < 8 { +func parseDFlowInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(data) < 8 { return nil, nil } var err error - disc := ix.Data[:8] - payload := ix.Data[8:] + disc := data[:8] + payload := data[8:] var params *dflowSwapParams switch { @@ -277,12 +272,12 @@ func parseDFlowInstruction(tx *versionedTransaction, instructionIndex int) (*TxS var ( srcIdx uint8 ) - if len(ix.Accounts) <= 6 { + if len(accounts) <= 6 { return nil, nil } - accounts := ix.Accounts[5:] + accounts = accounts[5:] for i, acctIdx := range accounts { - key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + key, err := tx.GetAccount(acctIdx) //getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) if err != nil { return nil, err } @@ -295,29 +290,31 @@ func parseDFlowInstruction(tx *versionedTransaction, instructionIndex int) (*TxS return nil, nil } - baseMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) + baseMint, err := tx.GetAccount(accounts[srcIdx]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) if err != nil { return nil, err } - quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) + quoteMint, err := tx.GetAccount(accounts[srcIdx+1]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) if err != nil { return nil, err } if !quoteMint.Equals(solana.WrappedSol) { return nil, nil } + maker, _ := tx.GetAccount(0) // Build TxSignal sig := &TxSignal{ - TxHash: tx.Signatures[0].String(), - Maker: tx.Message.StaticAccountKeys[0].String(), + TxHash: tx.Signatures(), + Maker: maker.String(), Program: "PumpAMM", Event: "sell", Token0Address: baseMint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(pump.Amount), Token1Amount: decimal.Zero, - Token0AmountUint64: uint64(pump.Amount), + Token0AmountUint64: pump.Amount, + Block: tx.Block(), Token1AmountUint64: 0, } return sig, nil diff --git a/pkg/shreder/entry.go b/pkg/shreder/entry.go new file mode 100644 index 0000000..65b3b6e --- /dev/null +++ b/pkg/shreder/entry.go @@ -0,0 +1,334 @@ +package shreder + +import ( + "encoding/binary" + "fmt" + "io" +) + +type constArray struct { + data []byte + size int + + offset int +} + +func newConstArray(data []byte) constArray { + return constArray{ + data: data, + size: len(data), + offset: 0, + } +} + +func (c *constArray) Len() int { + return c.size +} + +func (c *constArray) Offset() int { + return c.offset +} + +func (c *constArray) Read(cap int) ([]byte, error) { + if c.offset+cap > c.size { + return nil, io.EOF + } + c.offset += cap + return c.data[c.offset-cap : c.offset], nil +} + +func (c *constArray) ReadBytes() (byte, error) { + if c.offset >= c.size { + return 0, io.EOF + } + c.offset++ + return c.data[c.offset-1], nil +} + +func (c *constArray) PeekBytes() (byte, error) { + if c.offset >= c.size { + return 0, io.EOF + } + return c.data[c.offset], nil +} + +func (c *constArray) ReadU64() (uint64, error) { + if c.offset+8 > c.size { + return 0, io.EOF + } + c.offset += 8 + return binary.LittleEndian.Uint64(c.data[c.offset-8 : c.offset]), nil +} + +func (c *constArray) ReadCompactU16() (uint16, error) { + ln := 0 + size := 0 + for i := 0; i < 3; i++ { + if len(c.data[c.offset:]) == 0 { + return 0, fmt.Errorf("unable to decode compact u16 at %d: zero byte", i) + } + elem := int(c.data[c.offset+i]) + if elem == 0 && i != 0 { + return 0, fmt.Errorf("alias") + } + if i == 2 && (elem&0x80) != 0 { + return 0, fmt.Errorf("byte three continues") + } + ln |= (elem & 0x7f) << (size * 7) + size++ + if (elem & 0x80) == 0 { + break + } + } + c.offset += size + return uint16(ln), nil +} + +func (c *constArray) Skip(size int) error { + if c.offset+size > c.size { + return io.EOF + } + c.offset += size + return nil +} + +// entriesToVersionedTransaction converts raw entry bytes to versioned transactions. +func entriesToVersionedTransaction(slot uint64, b constArray) ([]*versionedTransaction, error) { + b.offset = 0 + + entriesNum, err := b.ReadU64() + if err != nil { + return nil, fmt.Errorf("unable to read entries num: %w", err) + } + if entriesNum == 0 { + return nil, nil + } + //preCap := b.Len() / 256 + //if preCap < int(entriesNum) { + // preCap = int(entriesNum) + //} + vs := make([]*versionedTransaction, 0, entriesNum) + + // logger.Debug("parsing entries", "count", entriesNum, "data len", b.Len(), "data", base64.StdEncoding.EncodeToString(b.data)) + for i := uint64(0); i < entriesNum; i++ { + err = b.Skip(40) + if err != nil { + return vs, fmt.Errorf("failed to skip num_hashes + hash of entry %d: %w", i, err) + } + numTx, err := b.ReadU64() + if err != nil { + return vs, fmt.Errorf("failed to read num_transactions of entry %d: %w", i, err) + } + for j := 0; j < int(numTx); j++ { + numSignatures, err := b.ReadCompactU16() + if err != nil { + return vs, fmt.Errorf("failed to read numSignatures in entry %d, txn %d: %w", i, j, err) + } + + // todo : enforce a maximum number of signatures to prevent OOM + if numSignatures > 16 { + return vs, fmt.Errorf("numSignatures %d exceeds maximum in entry %d, txn %d", numSignatures, i, j) + } + if numSignatures == 0 { + return vs, fmt.Errorf("numSignatures is zero in entry %d, txn %d", i, j) + } + + versioned := requireVersionedPool() // get a versioned transaction from the pool + vs = append(vs, versioned) + versioned.block = slot + versioned.bindArray = b.data + versioned.signatures = int(numSignatures) + versioned.signaturesOffset = b.Offset() + err = b.Skip(64 * int(numSignatures)) + if err != nil { + return vs, fmt.Errorf("unable to read signature in entry %d, txn %d: %w", i, j, err) + } + + msgVersion, err := b.PeekBytes() + if err != nil { + return vs, fmt.Errorf("unable to read message version in entry %d, txn %d: %w", i, j, err) + } + msgVersion = msgVersion & 0x80 >> 7 // mask to get only the version bits + legacy := msgVersion == 0 + headerSkip := 3 + if !legacy { + headerSkip = 4 + } + // skip msg version, mx.Header+3 + + err = b.Skip(headerSkip) + if err != nil { + return vs, fmt.Errorf("unable to skip message header in entry %d, txn %d: %w", i, j, err) + } + + // read mx.AccountKeys + // _, err = r.Read(u16[:]) + + numAccountKeys, err := b.ReadCompactU16() + // logger.Info("tx", "hash", versioned.Signatures[0].String(), "version", msgVersion) + if err != nil { + return vs, fmt.Errorf("unable to decode numAccountKeys in entry %d, txn %d: %w", i, j, err) + } + // todo : enforce a maximum number of account keys to prevent OOM + if numAccountKeys > 255 { + return vs, fmt.Errorf("numAccountKeys %d exceeds maximum in entry %d, txn %d", numAccountKeys, i, j) + } + versioned.staticAccountKeys = uint8(numAccountKeys) + + versioned.staticAccountKeysOffset = b.Offset() + err = b.Skip(32 * int(numAccountKeys)) + if err != nil { + return vs, fmt.Errorf("unable to read accountKey in entry %d, txn %d: %w", i, j, err) + } + + //skip solana hash + err = b.Skip(32) + if err != nil { + return vs, fmt.Errorf("unable to skip recentBlockhash in entry %d, txn %d: %w", i, j, err) + } + + // read mx.Instructions + numInstructions, err := b.ReadCompactU16() + if err != nil { + return vs, fmt.Errorf("unable to decode numInstructions in entry %d, txn %d: %w", i, j, err) + } + + // todo : enforce a maximum number of instructions to prevent OOM + if numInstructions >= 256 { + return vs, fmt.Errorf("numInstructions %d exceeds maximum in entry %d, txn %d, txHash: %s", numInstructions, i, j, versioned.Signatures()) + } + versioned.instructions = int(numInstructions) + if cap(versioned.Instrs) < int(numInstructions) { + versioned.Instrs = make([]compiledInstruction, numInstructions) + } else { + versioned.Instrs = versioned.Instrs[:numInstructions] + } + for k := 0; k < int(numInstructions); k++ { + versioned.Instrs[k].ProgramIDIndex, err = b.ReadBytes() + if err != nil { + return vs, fmt.Errorf("unable to read mx.Instructions[%d].ProgramIDIndex in entry %d, txn %d: %w", k, i, j, err) + } + numAccounts, err := b.ReadCompactU16() + if err != nil { + return vs, fmt.Errorf("unable to decode numAccounts for ix[%d] in entry %d, txn %d: %w", k, i, j, err) + } + + // todo : enforce a maximum number of accounts to prevent OOM + if numAccounts >= 256 { + return vs, fmt.Errorf("numAccounts %d exceeds maximum for ix[%d] in entry %d, txn %d", numAccounts, k, i, j) + } + versioned.Instrs[k].AccountsLen = int(numAccounts) + if numAccounts != 0 { + versioned.Instrs[k].AccountsOffset = b.Offset() + err = b.Skip(int(numAccounts)) + if err != nil { + return vs, fmt.Errorf("unable to read mx.Instructions[%d].Accounts in entry %d, txn %d: %w", k, i, j, err) + } + } + // _, err = r.Read(u16[:]) + dataLen, err := b.ReadCompactU16() + if err != nil { + return vs, fmt.Errorf("unable to decode mx.Instructions[%d].Data length in entry %d, txn %d: %w", k, i, j, err) + } + //todo : enforce a maximum data length to prevent OOM + if dataLen > 2048 { + return vs, fmt.Errorf("mx.Instructions[%d].Data length %d exceeds maximum in entry %d, txn %d, txHash: %s", k, dataLen, i, j, versioned.Signatures()) + } + versioned.Instrs[k].DataLen = int(dataLen) + if dataLen > 0 { + versioned.Instrs[k].DataOffset = b.Offset() + err = b.Skip(int(dataLen)) + if err != nil { + return vs, fmt.Errorf("unable to read mx.Instructions[%d].Data in entry %d, txn %d: %w", k, i, j, err) + } + } + } + + if !legacy { + // read mx.AddressTableLookups + numLookups, err := b.ReadBytes() + if err != nil { + return vs, fmt.Errorf("unable to read numAddressTableLookups in entry %d, txn %d: %w", i, j, err) + } + + if cap(versioned.ATL) < int(numLookups) { + versioned.ATL = make([]addressTableLookup, numLookups) + } else { + versioned.ATL = versioned.ATL[:numLookups] + } + versioned.addressTableLookups = int(numLookups) + + for k := uint8(0); k < numLookups; k++ { + versioned.ATL[k].AccountKeyOffset = b.Offset() + err = b.Skip(32) + if err != nil { + return vs, fmt.Errorf("unable to read address table account key for lookup[%d] in entry %d, txn %d: %w", k, i, j, err) + } + numWritable, err := b.ReadCompactU16() + if err != nil { + return vs, fmt.Errorf("unable to decode numWritableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err) + } + // todo : enforce a maximum number of writable indexes to prevent OOM + if numWritable >= 256 { + return vs, fmt.Errorf("numWritableIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numWritable, k, i, j) + } + versioned.ATL[k].WriteLen = int(numWritable) + if numWritable > 0 { + versioned.ATL[k].WriteOffset = b.Offset() + err = b.Skip(int(numWritable)) + if err != nil { + return vs, fmt.Errorf("unable to read writableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err) + } + } + // _, err = r.Read(u16[:]) + + numReadonly, err := b.ReadCompactU16() + if err != nil { + return vs, fmt.Errorf("unable to decode numReadonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err) + } + // todo : enforce a maximum number of readonly indexes to prevent OOM + if numReadonly > 256 { + return vs, fmt.Errorf("numReadonlyIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numReadonly, k, i, j) + } + versioned.ATL[k].ReadLen = int(numReadonly) + + if numReadonly > 0 { + versioned.ATL[k].ReadOffset = b.Offset() + err = b.Skip(int(numReadonly)) + if err != nil { + return vs, fmt.Errorf("unable to read readonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err) + } + } + } + } + } + } + // logger.Debug("parsing num_transactions of entry", "slot", slot, "count", entriesNum, "data len", b.Len(), "num_tx", uint32(len(vs))) + + //logger.Debug("parsing entries", "slot", slot, "count", entriesNum, "data len", b.Len(), "txns", len(vs)) + return vs, nil +} + +func decodeCompactU16(b []byte) (int, uint16, error) { + ln := 0 + size := 0 + for i := 0; i < 3; i++ { + if len(b) == 0 { + return 0, 0, fmt.Errorf("unable to decode compact u16 at %d: zero byte", i) + } + elem := int(b[0]) + b = b[1:] + if elem == 0 && i != 0 { + return 0, 0, fmt.Errorf("alias") + } + if i == 2 && (elem&0x80) != 0 { + return 0, 0, fmt.Errorf("byte three continues") + } + ln |= (elem & 0x7f) << (size * 7) + size++ + if (elem & 0x80) == 0 { + break + } + } + return size, uint16(ln), nil +} diff --git a/pkg/shreder/entry_test.go b/pkg/shreder/entry_test.go new file mode 100644 index 0000000..e3b2734 --- /dev/null +++ b/pkg/shreder/entry_test.go @@ -0,0 +1,41 @@ +package shreder + +import ( + "encoding/base64" + "testing" +) + +func TestDecodeEntry(t *testing.T) { + tests := []struct { + name string + data string + }{ + { + name: "TestDecodeEntry1", + data: "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", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, _ := base64.StdEncoding.DecodeString(tt.data) + _, err := entriesToVersionedTransaction(0, newConstArray(data)) + if err != nil { + t.Errorf("decodeEntry() error = %v", err) + return + } + }) + } +} + +func BenchmarkDecodeEntry(b *testing.B) { + data, _ := base64.StdEncoding.DecodeString( + "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", + ) + //constArray1 := newConstArray(data) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = entriesToVersionedTransaction(0, newConstArray(data)) + } +} diff --git a/pkg/shreder/juptierv6.go b/pkg/shreder/juptierv6.go index 8f02204..bdaa051 100644 --- a/pkg/shreder/juptierv6.go +++ b/pkg/shreder/juptierv6.go @@ -859,21 +859,16 @@ func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) { } // only decodes inputIdx = 0 container pumpSwap instructions for now -func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseJupiterV6Instruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if len(instruction.Data) < 8 { + if len(data) < 8 { return nil, nil } - disc := instruction.Data[:8] + disc := data[:8] var ( sourceMint solana.PublicKey @@ -886,26 +881,26 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( // route/shared_accounts_* (v1) use different account layouts; we only decode args here. switch { case bytes.Equal(disc, jupiterRouteV2): - args, err := decodeJupiterV6RouteV2Arg(instruction.Data[8:]) + args, err := decodeJupiterV6RouteV2Arg(data[8:]) if err != nil { return nil, err } inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan) case bytes.Equal(disc, jupiterSharedAccountsRouteV2): - args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:]) + args, err := decodeJupiterV6SharedAccountsRouteV2Arg(data[8:]) if err != nil { return nil, err } inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan) case bytes.Equal(disc, jupiterRoute): - args, err := decodeJupiterV6RouteArg(instruction.Data[8:]) + args, err := decodeJupiterV6RouteArg(data[8:]) if err != nil { return nil, err } _ = args inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) case bytes.Equal(disc, jupiterSharedAccountsRoute): - args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:]) + args, err := decodeJupiterV6SharedAccountsRouteArg(data[8:]) if err != nil { return nil, err } @@ -916,7 +911,8 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( } if planCount > 1 { // multiple pumpSwapSell at inputIdx=0? should not happen - logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", planCount) + logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures(), "planCount", planCount) + return nil, nil } if inputAmount == 0 { return nil, nil @@ -924,10 +920,10 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( // existing mint extraction logic only valid for route_v2/ exact_out_route_v2. Keep it but guard. if bytes.Equal(disc, jupiterRouteV2) || bytes.Equal(disc, jupiterSharedAccountsRouteV2) { - if len(instruction.Accounts) < 6 { + if len(accounts) < 6 { return nil, fmt.Errorf("not enough accounts for jupiter v6 v2 instruction") } - sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[3])) + sourceMint, err = tx.GetAccount(accounts[3]) //getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } @@ -935,12 +931,12 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( var ( srcIdx uint8 ) - if len(instruction.Accounts) <= 9 { + if len(accounts) <= 9 { return nil, nil } - accounts := instruction.Accounts[8:] + accounts = accounts[8:] for i, acctIdx := range accounts { - key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + key, err := tx.GetAccount(acctIdx) // getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) if err != nil { return nil, err } @@ -953,14 +949,14 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( return nil, nil } - baseMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) + baseMint, err := tx.GetAccount(accounts[srcIdx]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) if err != nil { return nil, err } if !sourceMint.Equals(baseMint) { return nil, nil } - quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) + quoteMint, err := tx.GetAccount(accounts[srcIdx+1]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) if err != nil { return nil, err } @@ -969,22 +965,22 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( } } else if bytes.Equal(disc, jupiterSharedAccountsRoute) { - if len(instruction.Accounts) < 12 { + if len(accounts) < 12 { return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterSharedAccountsRoute instruction") } - sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[7])) + sourceMint, err = tx.GetAccount(accounts[7]) // getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[7])) if err != nil { return nil, err } var ( srcIdx uint8 ) - if len(instruction.Accounts) <= 12 { + if len(accounts) <= 12 { return nil, nil } - accounts := instruction.Accounts[11:] + accounts = accounts[11:] for i, acctIdx := range accounts { - key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + key, err := tx.GetAccount(acctIdx) // getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) if err != nil { return nil, err } @@ -997,7 +993,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( return nil, nil } - baseMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) + baseMint, err := tx.GetAccount(accounts[srcIdx]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) if err != nil { return nil, err } @@ -1005,7 +1001,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( return nil, nil } - quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) + quoteMint, err := tx.GetAccount(accounts[srcIdx+1]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) if err != nil { return nil, err } @@ -1013,16 +1009,16 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( return nil, nil } } else { - if len(instruction.Accounts) < 10 { + if len(accounts) < 10 { return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterRoute instruction") } var ( srcIdx uint8 ) - accounts := instruction.Accounts[9:] + accounts = accounts[9:] for i, acctIdx := range accounts { - key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + key, err := tx.GetAccount(acctIdx) // getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) if err != nil { return nil, err } @@ -1034,12 +1030,12 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( if srcIdx == 0 || srcIdx+1 >= uint8(len(accounts)) { return nil, nil } - sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) + sourceMint, err = tx.GetAccount(accounts[srcIdx]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) if err != nil { return nil, err } - quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) + quoteMint, err := tx.GetAccount(accounts[srcIdx+1]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) if err != nil { return nil, err } @@ -1048,9 +1044,10 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( } } + maker, _ := tx.GetAccount(0) signal := &TxSignal{ - TxHash: tx.Signatures[0].String(), - Maker: tx.Message.StaticAccountKeys[0].String(), + TxHash: tx.Signatures(), + Maker: maker.String(), Token0Address: sourceMint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(inputAmount), @@ -1060,7 +1057,7 @@ func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) ( IsToken2022: false, IsMayhemMode: false, ExactSOL: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: inputAmount, Token1AmountUint64: 0, } diff --git a/pkg/shreder/okxonchainlab.go b/pkg/shreder/okxonchainlab.go index b1c546b..80de91d 100644 --- a/pkg/shreder/okxonchainlab.go +++ b/pkg/shreder/okxonchainlab.go @@ -245,17 +245,13 @@ type OkxV2SwapScorch struct { Id [16]byte } -func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } - ix := msg.Instructions[instructionIndex] - if len(ix.Data) < 8 { +func parseOkxDexRouteV2Instruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + + if len(data) < 8 { return nil, nil } - disc := ix.Data[:8] - data := ix.Data[8:] + disc := data[:8] + data = data[8:] var ( args *OkxV2SwapArgs @@ -288,8 +284,8 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in default: return nil, nil } - if len(ix.Accounts) < 15 { - return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts)) + if len(accounts) < 15 { + return nil, fmt.Errorf("invalid account count: %d", len(accounts)) } var ( inputAmount uint64 @@ -303,24 +299,24 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in } } if routeCount > 1 { - logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", routeCount) + logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures(), "routeCount", routeCount) return nil, nil } if inputAmount == 0 { return nil, nil } - srcMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(ix.Accounts[3])) + srcMint, err := tx.GetAccount(accounts[3]) //getStaticKey(tx.Message.StaticAccountKeys, int(ix.Accounts[3])) var ( srcIdx uint8 ) - if len(ix.Accounts) <= 15 { + if len(accounts) <= 15 { return nil, nil } - accounts := ix.Accounts[14:] + accounts = accounts[14:] for i, acctIdx := range accounts { - key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + key, err := tx.GetAccount(acctIdx) // getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) if err != nil { return nil, err } @@ -333,7 +329,7 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in return nil, nil } - baseMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) + baseMint, err := tx.GetAccount(accounts[srcIdx]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) if err != nil { return nil, err } @@ -341,17 +337,18 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in return nil, nil } - quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) + quoteMint, err := tx.GetAccount(accounts[srcIdx+1]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1])) if err != nil { return nil, err } if !quoteMint.Equals(solana.WrappedSol) { return nil, nil } + maker, _ := tx.GetAccount(0) return &TxSignal{ - TxHash: tx.Signatures[0].String(), - Maker: tx.Message.StaticAccountKeys[0].String(), + TxHash: tx.Signatures(), + Maker: maker.String(), Token0Address: baseMint.String(), Token1Address: wsolMint, Token0Amount: formatTokenAmount(inputAmount), @@ -363,6 +360,7 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in IsMayhemMode: false, ExactSOL: false, Token0AmountUint64: inputAmount, + Block: tx.Block(), Token1AmountUint64: 0, }, nil } diff --git a/pkg/shreder/tx.go b/pkg/shreder/tx.go index 8b67637..98c26de 100644 --- a/pkg/shreder/tx.go +++ b/pkg/shreder/tx.go @@ -52,8 +52,7 @@ type TxSignal struct { Token0AmountUint64 uint64 `json:"-"` Token1AmountUint64 uint64 `json:"-"` - ParseStart time.Time `json:"parse_start"` - ParseEnd time.Time `json:"parse_end"` + Stats Stats `json:"-"` } func (t *TxSignal) Parse() *TxSignal { diff --git a/pkg/shreder/txparser.go b/pkg/shreder/txparser.go index 3b1d54b..75d5850 100644 --- a/pkg/shreder/txparser.go +++ b/pkg/shreder/txparser.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "math/big" + "slices" "strings" "sync" "time" @@ -104,29 +105,174 @@ var ( bonkBuyAndSellTokensIX = []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a} ) -type compiledInstruction struct { - ProgramIDIndex uint8 - Accounts []uint8 - Data []byte -} - type addressTableLookup struct { - AccountKey solana.PublicKey - WritableIndexes []uint8 - ReadonlyIndexes []uint8 + AccountKeyOffset int + WriteOffset int + WriteLen int + ReadOffset int + ReadLen int } -type versionedMessage struct { - StaticAccountKeys []solana.PublicKey - Instructions []compiledInstruction - AddressTableLookups []addressTableLookup +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 { - Signatures []solana.Signature - Message versionedMessage - Block uint64 - Time time.Time + 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 { @@ -203,36 +349,23 @@ type fjszBuyArgs struct { var ( versionedPool = sync.Pool{} - - accIdxPool = sync.Pool{} ) -func requireAccIdxSlice() []uint8 { - v := accIdxPool.Get() - if v == nil { - return make([]uint8, 0, 16) - } - return v.([]uint8) -} - -func releaseAccIdxSlice(s []uint8) { - if s == nil { - return - } - s = s[:0] - accIdxPool.Put(s) +type compiledInstruction struct { + ProgramIDIndex uint8 + AccountsLen int + AccountsOffset int + DataOffset int + DataLen int } func requireVersionedPool() *versionedTransaction { v := versionedPool.Get() if v == nil { return &versionedTransaction{ - Signatures: make([]solana.Signature, 0, 10), - Message: versionedMessage{ - StaticAccountKeys: make([]solana.PublicKey, 0, 256), - Instructions: make([]compiledInstruction, 0, 16), - AddressTableLookups: make([]addressTableLookup, 0, 10), - }, + TableLookups: make([]solana.PublicKey, 0, 16), + Instrs: make([]compiledInstruction, 0, 16), + ATL: make([]addressTableLookup, 0, 4), } } return v.(*versionedTransaction) @@ -242,50 +375,180 @@ func releaseVersionedPool(v *versionedTransaction) { if v == nil { return } - for i := range v.Message.Instructions { - releaseAccIdxSlice(v.Message.Instructions[i].Accounts) - } - for i := range v.Message.AddressTableLookups { - releaseAccIdxSlice(v.Message.AddressTableLookups[i].WritableIndexes) - releaseAccIdxSlice(v.Message.AddressTableLookups[i].ReadonlyIndexes) - } + 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) } -// ParseTransaction mirrors the Rust parse_transaction entry point. -func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables, stats bool) []*TxSignal { - var now time.Time +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 { - now = time.Now() + 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 || len(versioned.Signatures) == 0 { - return nil + if err != nil || versioned == nil || versioned.signatures == 0 { + return } defer func() { releaseVersionedPool(versioned) }() - txHash := versioned.Signatures[0] - // staticKeys := versioned.Message.StaticAccountKeys - instructions := versioned.Message.Instructions + parseTransaction(stat, versioned, loader, txCh) +} - if loader != nil && len(versioned.Message.AddressTableLookups) > 0 { +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 _, lookup := range versioned.Message.AddressTableLookups { - if len(lookup.WritableIndexes) == 0 { + for i := 0; i < versioned.addressTableLookups; i++ { + lookups, idxs, _, err := versioned.GetAddressTableLookups(i) + if err != nil { continue } - lookupTableOk = loader.FillToTx(versioned, lookup.AccountKey, lookup.WritableIndexes) + if len(idxs) == 0 { + continue + } + lookupTableOk = loader.FillToTx(versioned, lookups, idxs) if !lookupTableOk { break } } if lookupTableOk { - for _, lookup := range versioned.Message.AddressTableLookups { - if len(lookup.ReadonlyIndexes) == 0 { + for i := 0; i < versioned.addressTableLookups; i++ { + + lookups, _, idxs, err := versioned.GetAddressTableLookups(i) + if err != nil { continue } - lookupTableOk = loader.FillToTx(versioned, lookup.AccountKey, lookup.ReadonlyIndexes) + if len(idxs) == 0 { + continue + } + lookupTableOk = loader.FillToTx(versioned, lookups, idxs) if !lookupTableOk { break } @@ -294,127 +557,58 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables, // versioned.Message.StaticAccountKeys = staticKeys } - var parsed []*TxSignal = make([]*TxSignal, 0, 3) + //instructions := versioned.GetInstruction() - for i := range instructions { - inst := instructions[i] - if int(inst.ProgramIDIndex) >= len(versioned.Message.StaticAccountKeys) { + for i := 0; i < versioned.GetInstructionLen(); i++ { + programID, accounts, data, err := versioned.GetInstruction(i) + if err != nil { continue } - - programID := versioned.Message.StaticAccountKeys[inst.ProgramIDIndex] - switch programID { - case pumpProgramID: - txRes, err := parsePumpInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "pump") - case azczProgramID: - txRes, err := parseAzczInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "azcz") - case f5tfProgramID: - txRes, err := parseF5tfInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "f5tf") - case flasProgramID: - txRes, err := parseFlasInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "flas") - case photonProgramID: - txRes, err := parsePhotonInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "photon") - case pumpAmmProgramID: - txRes, err := parsePumpAmmInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "pumpamm") - case boboProgramID: - txRes, err := parseBoboInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "bobo") - case qtkvProgramID: - txRes, err := parseQtkvInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "qtkv") - case fjszProgramID: - txRes, err := parseFjszInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "fjsz") - case terminalProgramID: - txRes, err := parseTermInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "terminal") - case jupiterV6ProgramID: - txRes, err := parseJupiterV6Instruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "jupiterv6") - case okxDexRouteV2ProgramID: - txRes, err := parseOkxDexRouteV2Instruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "okxdexroutev2") - case dflowProgramID: - txRes, err := parseDFlowInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "dflow") - case gmgnProgramID: - txRes, err := parseGMGNInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "gmgn") - case bonkProgramID: - txRes, err := parseBonkInstruction(versioned, i) - parsed = appendParsed(now, parsed, txRes, err, txHash, "bonk") + handler, ok := parsedMap[programID] + if !ok { + continue } + txRes, err := handler.Func(versioned, accounts, data) + appendParsed(stat, parsed, txRes, err, versioned, handler.Label) + } - return parsed + return } -func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal { +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", base58.Encode(txHash[:])) + logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", tx.Signatures()) } - return list + return } if parsed != nil { parsed.Label = label - if !start.IsZero() { - parsed.ParseEnd = time.Now() - parsed.ParseStart = start + if !stat.Start.IsZero() { + stat.Done = time.Now() + parsed.Stats = stat } - list = append(list, parsed) + txCh <- *parsed + } else { + // logger.Debug("txparser: no parsed result", "label", label, "tx_hash", tx.Signatures()) } - return list + 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 = versioned.Signatures[:0] - for _, rawSig := range protoTx.Signatures { - versioned.Signatures = append(versioned.Signatures, solana.SignatureFromBytes(rawSig)) - } - versioned.Message.StaticAccountKeys = versioned.Message.StaticAccountKeys[:0] - for _, key := range msg.AccountKeys { - versioned.Message.StaticAccountKeys = append(versioned.Message.StaticAccountKeys, solana.PublicKeyFromBytes(key)) - } - versioned.Message.Instructions = versioned.Message.Instructions[:0] - for _, instr := range msg.Instructions { - accounts := requireAccIdxSlice() - accounts = append(accounts, instr.Accounts...) - versioned.Message.Instructions = append(versioned.Message.Instructions, - compiledInstruction{ - ProgramIDIndex: uint8(instr.ProgramIdIndex), - Accounts: accounts, - Data: instr.Data, - }) - } - - versioned.Message.AddressTableLookups = versioned.Message.AddressTableLookups[:0] - for _, lookup := range msg.AddressTableLookups { - writable := requireAccIdxSlice() - writable = append(writable, lookup.WritableIndexes...) - readonly := requireAccIdxSlice() - readonly = append(readonly, lookup.ReadonlyIndexes...) - versioned.Message.AddressTableLookups = append(versioned.Message.AddressTableLookups, addressTableLookup{ - AccountKey: solana.PublicKeyFromBytes(lookup.AccountKey), - WritableIndexes: writable, - ReadonlyIndexes: readonly, - }) - } - - versioned.Block = update.GetSlot() + 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 } @@ -428,54 +622,40 @@ func formatSolAmount(lamports uint64) decimal.Decimal { return val.Div(decimal.NewFromInt(1_000_000_000)) } -func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) { - if index < 0 || index >= len(static) { - return solana.PublicKey{}, NewAccountNotFoundError(index, len(static)) - } - return static[index], nil -} - -func parsePumpInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } - - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) < 8 { +func parsePumpInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(data) < 8 { return nil, fmt.Errorf("data is empty") } - if matchMethod(instruction.Data[0:8], pumpBuyV2TokensIX) || matchMethod(instruction.Data[0:8], pumpBuyTokensIX) { - return parsePumpBuy(tx, &instruction) - } else if matchMethod(instruction.Data[0:8], pumpExtendedSellIX) { - return parsePumpSell(tx, &instruction) - } else if matchMethod(instruction.Data[0:8], pumpCreateCoinIX) { - return parsePumpCreate(tx, &instruction) - } else if matchMethod(instruction.Data[0:8], pumpCreateCoinV2IX) { - return parsePumpCreateV2(tx, &instruction) + 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 *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parsePumpCreate(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) + mint, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0])) if err != nil { return nil, err } - creator, err := getStaticKey(staticKeys, int(instruction.Accounts[7])) + creator, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7])) if err != nil { return nil, err } return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "pump", Maker: creator.String(), Token0Address: mint.String(), @@ -486,37 +666,36 @@ func parsePumpCreate(tx *versionedTransaction, instruction *compiledInstruction) Event: "create", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: 0, Token1AmountUint64: 0, }, nil } -func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parsePumpCreateV2(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 8 { - return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(instruction.Data)) + if len(data) < 8 { + return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) + mint, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0])) if err != nil { return nil, err } - tokenProgramKey, err := getStaticKey(staticKeys, int(instruction.Accounts[7])) + 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, instruction.Data[8:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "pump", Maker: args.Creator.String(), Token0Address: mint.String(), @@ -527,7 +706,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio Event: "create", IsToken2022: tokenProgramKey.String() != tokenProgram, IsMayhemMode: args.IsMayhemMode, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: 0, Token1AmountUint64: 0, }, nil @@ -552,35 +731,34 @@ func decodePumpBuyArgs(data []byte) (uint64, uint64, error) { return 0, 0, fmt.Errorf("failed to parse buy tokens args") } -func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - amount, sol, err := decodePumpBuyArgs(instruction.Data) +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(instruction.Data, pumpBuyV2TokensIX) { + if matchMethod(data, pumpBuyV2TokensIX) { temp := amount amount = sol sol = temp exactIn = true } - if len(instruction.Accounts) < 7 { + if len(accounts) < 7 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) if err != nil { return nil, err } - buyer, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + buyer, err := tx.GetAccount(accounts[6]) if err != nil { return nil, err } return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "pump", Maker: buyer.String(), Token0Address: mint.String(), @@ -592,7 +770,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (* ExactSOL: exactIn, IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: amount, Token1AmountUint64: sol, }, nil @@ -617,28 +795,27 @@ func decodePumpSellArgs(data []byte) (uint64, uint64, error) { return 0, 0, fmt.Errorf("failed to parse sell tokens args") } -func parsePumpSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - amount, minSol, err := decodePumpSellArgs(instruction.Data) +func parsePumpSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + amount, minSol, err := decodePumpSellArgs(data) if err != nil { return nil, err } - if len(instruction.Accounts) < 7 { + if len(accounts) < 7 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) if err != nil { return nil, err } - seller, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + seller, err := tx.GetAccount(accounts[6]) if err != nil { return nil, err } return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "pump", Maker: seller.String(), Token0Address: mint.String(), @@ -649,55 +826,47 @@ func parsePumpSell(tx *versionedTransaction, instruction *compiledInstruction) ( Event: "sell", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: amount, Token1AmountUint64: minSol, }, nil } -func parseAzczInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } - - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { +func parseAzczInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if matchMethod(instruction.Data, azczBuyTokensIX) { - return parseAzczBuy(tx, instructionIndex) - } else if matchMethod(instruction.Data, azczAmmBuyTokensIX) { - return parseAzczAmmBuy(tx, instructionIndex) + 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 *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 8 { +func parseAzczAmmBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + mint, err := tx.GetAccount(accounts[3]) // getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } - if len(instruction.Data) < 17 { - return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(instruction.Data)) + if len(data) < 17 { + return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(data)) } - solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9]) + solAmount := binary.LittleEndian.Uint64(data[1:9]) return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "azcz", Maker: user.String(), Token0Address: mint.String(), @@ -709,39 +878,37 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: 0, Token1AmountUint64: solAmount, }, nil } -func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 8 { +func parseAzczBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[7])) + user, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7])) if err != nil { return nil, err } - if len(instruction.Data) < 2 { - return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(instruction.Data)) + 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, instruction.Data[1:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "azcz", Maker: user.String(), Token0Address: mint.String(), @@ -752,51 +919,45 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er Event: "buy", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.TokenAmount, Token1AmountUint64: args.SolAmount, }, nil } -func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseF5tfInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if !matchMethod(instruction.Data, f5tfBuyTokensIX) { + if !matchMethod(data, f5tfBuyTokensIX) { return nil, nil } - if len(instruction.Accounts) < 7 { + if len(accounts) < 7 { return nil, fmt.Errorf("accounts too short") } - staticKeys := msg.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6])) if err != nil { return nil, err } - if len(instruction.Data) < 2 { - return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(instruction.Data)) + 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, instruction.Data[1:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "f5tf", Maker: user.String(), Token0Address: mint.String(), @@ -807,67 +968,59 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi Event: "buy", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.TokenAmount, Token1AmountUint64: args.SolAmount, }, nil } -func parseFlasInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } - - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { +func parseFlasInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if len(instruction.Data) == 10 && instruction.Data[0] == 1 { + if len(data) == 10 && data[0] == 1 { return nil, nil } - if len(instruction.Data) < 20 { - return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data)) + if len(data) < 20 { + return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(data)) } - methodData := instruction.Data[17:20] + methodData := data[17:20] //if !matchMethod(methodData, flasBuyTokensIX) { // return nil, nil //} if matchMethod(methodData, flasBuyTokensIX) { - return parseFlasBuy(tx, instructionIndex) + return parseFlasBuy(tx, accounts, data) } else if matchMethod(methodData, flasSellTokensIX) { - return parseFlasSell(tx, instructionIndex) + return parseFlasSell(tx, accounts, data) } else if matchMethod(methodData, flasAmmBuyTokensIX) { - return parseFlasAmmBuy(tx, instructionIndex) + return parseFlasAmmBuy(tx, accounts, data) } else if matchMethod(methodData, flasAmmSellTokensIX) { - return parseFlasAmmSell(tx, instructionIndex) + return parseFlasAmmSell(tx, accounts, data) } return nil, nil } -func parseFlasAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 10 { +func parseFlasAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 10 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[9])) + mint, err := tx.GetAccount(accounts[9]) //getStaticKey(staticKeys, int(instruction.Accounts[9])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + 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, instruction.Data[1:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "flas", Maker: user.String(), Token0Address: mint.String(), @@ -879,35 +1032,33 @@ func parseFlasAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal IsToken2022: false, IsMayhemMode: false, ExactSOL: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.Amount1, Token1AmountUint64: args.Amount2, }, nil } -func parseFlasAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 10 { +func parseFlasAmmBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 10 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[9])) + mint, err := tx.GetAccount(accounts[9]) //getStaticKey(staticKeys, int(instruction.Accounts[9])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + 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, instruction.Data[1:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "flas", Maker: user.String(), Token0Address: mint.String(), @@ -919,35 +1070,33 @@ func parseFlasAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: 0, Token1AmountUint64: args.Amount1, }, nil } -func parseFlasSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 9 { +func parseFlasSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 9 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[8])) + mint, err := tx.GetAccount(accounts[8]) //getStaticKey(staticKeys, int(instruction.Accounts[8])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + 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, instruction.Data[1:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "flas", Maker: user.String(), Token0Address: mint.String(), @@ -958,37 +1107,35 @@ func parseFlasSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e Event: "sell", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.Amount1, Token1AmountUint64: args.Amount2, }, nil } -func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 9 { +func parseFlasBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 9 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[8])) + mint, err := tx.GetAccount(accounts[8]) //getStaticKey(staticKeys, int(instruction.Accounts[8])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) + user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0])) if err != nil { return nil, err } - if len(instruction.Data) > 20 { - instruction.Data = instruction.Data[:20] + if len(data) > 20 { + data = data[:20] } var args flasArgs - if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "flas", Maker: user.String(), Token0Address: mint.String(), @@ -1000,55 +1147,49 @@ func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.Amount2, Token1AmountUint64: args.Amount1, }, nil } -func parseGMGNInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseGMGNInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if len(instruction.Data) < 8 { + if len(data) < 8 { return nil, nil } - if matchMethod(instruction.Data, gmgnBuyTokensIX) { - return parseGMGNBuy(tx, &instruction) + if matchMethod(data, gmgnBuyTokensIX) { + return parseGMGNBuy(tx, accounts, data) } return nil, nil } -func parseGMGNBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parseGMGNBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 24 { - return nil, fmt.Errorf("data too short for gmgn buy args, len=%d", len(instruction.Data)) + if len(data) < 24 { + return nil, fmt.Errorf("data too short for gmgn buy args, len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6])) if err != nil { return nil, err } - solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16]) - tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24]) + solAmount := binary.LittleEndian.Uint64(data[8:16]) + tokenAmount := binary.LittleEndian.Uint64(data[16:24]) return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "gmgn", Maker: user.String(), Token0Address: mint.String(), @@ -1060,62 +1201,56 @@ func parseGMGNBuy(tx *versionedTransaction, instruction *compiledInstruction) (* IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: tokenAmount, Token1AmountUint64: solAmount, }, nil } -func parsePhotonInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parsePhotonInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if len(instruction.Data) < 8 { + if len(data) < 8 { return nil, nil } switch { - case bytes.Equal(instruction.Data[:8], photonBuyPumpTokensIX): - return parsePhotonBuy(tx, &instruction) - case bytes.Equal(instruction.Data[:8], photonSwapPumpAmmIX): - return parsePhotonSwap(tx, &instruction) + 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 *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parsePhotonBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data)) + if len(data) < 16 { + return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[7])) + 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, instruction.Data[8:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "photon", Maker: user.String(), Token0Address: mint.String(), @@ -1127,27 +1262,26 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction) IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.TokenAmount, Token1AmountUint64: solAmount, }, nil } -func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parsePhotonSwap(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data)) + if len(data) < 16 { + return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - base, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + base, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) + quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } @@ -1155,13 +1289,13 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) return nil, nil } - buyer, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + 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, instruction.Data[8:]); err != nil { + if err := borsh.Deserialize(&args, data[8:]); err != nil { return nil, fmt.Errorf("failed to parse swap pump amm tokens args: %w", err) } @@ -1172,7 +1306,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) solAmount := args.FromAmount * (100000000 - 1234568) / 100000000 return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "photon", Maker: buyer.String(), Token0Address: base.String(), @@ -1183,78 +1317,67 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) Event: "buy", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.ToAmount, Token1AmountUint64: solAmount, }, nil } -func parsePumpAmmInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parsePumpAmmInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if matchMethod(instruction.Data, pumpAmmBuyTokensIX) || matchMethod(instruction.Data, pumpAmmBuyTokensV2IX) { - return parsePumpAmmBuy(tx, &instruction) - } else if matchMethod(instruction.Data, pumpAmmSellTokensIX) { - return parsePumpAmmSell(tx, &instruction) + 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 *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseTermInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if len(instruction.Data) < 24 { + if len(data) < 24 { return nil, nil } switch { - case bytes.Equal(instruction.Data[:8], terminalBuyTokensIX): - return parseTermBuy(tx, &instruction) - case bytes.Equal(instruction.Data[:8], terminalSellTokensIX): - return parseTermSell(tx, &instruction) - case bytes.Equal(instruction.Data[:8], terminalAmmSellTokensIX): - return parseTermAmmSell(tx, &instruction) + 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 *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parseTermAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + user, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } - solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16]) - tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24]) + solAmount := binary.LittleEndian.Uint64(data[8:16]) + tokenAmount := binary.LittleEndian.Uint64(data[16:24]) return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "term", Maker: user.String(), Token0Address: mint.String(), @@ -1266,31 +1389,30 @@ func parseTermAmmSell(tx *versionedTransaction, instruction *compiledInstruction IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: tokenAmount, Token1AmountUint64: solAmount, }, nil } -func parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parseTermBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) // getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + user, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6])) if err != nil { return nil, err } - solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16]) - tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24]) + solAmount := binary.LittleEndian.Uint64(data[8:16]) + tokenAmount := binary.LittleEndian.Uint64(data[16:24]) return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "term", Maker: user.String(), Token0Address: mint.String(), @@ -1302,31 +1424,30 @@ func parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (* IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: tokenAmount, Token1AmountUint64: solAmount, }, nil } -func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parseTermSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + user, err := tx.GetAccount(accounts[6]) //getStaticKey(staticKeys, int(instruction.Accounts[6])) if err != nil { return nil, err } - tokenAmount := binary.LittleEndian.Uint64(instruction.Data[8:16]) - solAmount := binary.LittleEndian.Uint64(instruction.Data[16:24]) + tokenAmount := binary.LittleEndian.Uint64(data[8:16]) + solAmount := binary.LittleEndian.Uint64(data[16:24]) return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "term", Maker: user.String(), Token0Address: mint.String(), @@ -1338,7 +1459,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) ( IsToken2022: false, IsMayhemMode: false, ExactSOL: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: tokenAmount, Token1AmountUint64: solAmount, }, nil @@ -1363,29 +1484,28 @@ func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) { return 0, 0, fmt.Errorf("failed to parse buy tokens args") } -func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - amount, maxSol, err := decodePumpAmmBuyArgs(instruction.Data) +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(instruction.Data, pumpAmmBuyTokensV2IX) { + if matchMethod(data, pumpAmmBuyTokensV2IX) { temp := amount amount = maxSol maxSol = temp exactIn = true } - if len(instruction.Accounts) < 7 { + if len(accounts) < 7 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - base, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + base, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) + quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } @@ -1393,13 +1513,13 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) return nil, nil } - buyer, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), @@ -1411,28 +1531,27 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) IsToken2022: false, IsMayhemMode: false, ExactSOL: exactIn, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: amount, Token1AmountUint64: maxSol, }, nil } -func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - amount, minSol, err := decodePumpAmmBuyArgs(instruction.Data) +func parsePumpAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + amount, minSol, err := decodePumpAmmBuyArgs(data) if err != nil { return nil, err } - if len(instruction.Accounts) < 7 { + if len(accounts) < 7 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - base, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + base, err := tx.GetAccount(accounts[3]) // getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) + quote, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } @@ -1440,13 +1559,13 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction return nil, nil } - buyer, err := getStaticKey(staticKeys, int(instruction.Accounts[1])) + buyer, err := tx.GetAccount(accounts[1]) //getStaticKey(staticKeys, int(instruction.Accounts[1])) if err != nil { return nil, err } return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), @@ -1457,50 +1576,44 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction Event: "sell", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: amount, Token1AmountUint64: minSol, }, nil } -func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseBoboInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if len(instruction.Data) < 8 || !bytes.Equal(instruction.Data[:8], boboBuyPumpTokensIX) { + if len(data) < 8 || !bytes.Equal(data[:8], boboBuyPumpTokensIX) { return nil, nil } - if len(instruction.Accounts) < 8 { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(instruction.Data)) + if len(data) < 16 { + return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + 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, instruction.Data[8:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "bobo", Maker: user.String(), Token0Address: mint.String(), @@ -1511,57 +1624,50 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi Event: "buy", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: 1, Token1AmountUint64: args.SolAmount, }, nil } -func parseQtkvInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseQtkvInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if matchMethod(instruction.Data, qtkvBuyTokensIX) { - return parseQtkvBuy(tx, instructionIndex) - } else if matchMethod(instruction.Data, qtkvAmmSellTokensIX) { - return parseQtkvAmmSell(tx, instructionIndex) - } else if matchMethod(instruction.Data, qtkvSellTokensIX) { - return parseQtkvSell(tx, instructionIndex) + 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 *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 11 { +func parseQtkvSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 11 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 24 { - return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(instruction.Data)) + if len(data) < 24 { + return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[10])) + mint, err := tx.GetAccount(accounts[10]) //getStaticKey(staticKeys, int(instruction.Accounts[10])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) + 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(instruction.Data[19:25]) + tokenAmount := binary.LittleEndian.Uint64(data[19:25]) return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), @@ -1572,35 +1678,33 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e Event: "sell", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: tokenAmount, Token1AmountUint64: 0, }, nil } -func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 11 { +func parseQtkvAmmSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 11 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 24 { - return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(instruction.Data)) + if len(data) < 24 { + return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[10])) + mint, err := tx.GetAccount(accounts[10]) //getStaticKey(staticKeys, int(instruction.Accounts[10])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) + 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(instruction.Data[19:25]) + tokenAmount := binary.LittleEndian.Uint64(data[19:25]) return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), @@ -1611,35 +1715,33 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal Event: "sell", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: tokenAmount, Token1AmountUint64: 0, }, nil } -func parseQtkvBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - instruction := tx.Message.Instructions[instructionIndex] - if len(instruction.Accounts) < 8 { +func parseQtkvBuy(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[3])) + mint, err := tx.GetAccount(accounts[3]) //getStaticKey(staticKeys, int(instruction.Accounts[3])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) + 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, instruction.Data[1:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), @@ -1650,50 +1752,44 @@ func parseQtkvBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er Event: "buy", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.TokenNumber, Token1AmountUint64: args.SolAmount, }, nil } -func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseFjszInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if !matchMethod(instruction.Data, fjszBuyTokensIX) { + if !matchMethod(data, fjszBuyTokensIX) { return nil, nil } - if len(instruction.Accounts) < 7 { + if len(accounts) < 7 { return nil, fmt.Errorf("accounts too short") } - if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(instruction.Data)) + if len(data) < 16 { + return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(data)) } - staticKeys := tx.Message.StaticAccountKeys - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[2])) + mint, err := tx.GetAccount(accounts[2]) //getStaticKey(staticKeys, int(instruction.Accounts[2])) if err != nil { return nil, err } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + 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, instruction.Data[8:]); err != nil { + 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[0].String(), + TxHash: tx.Signatures(), Label: "fjsz", Maker: user.String(), Token0Address: mint.String(), @@ -1704,36 +1800,30 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi Event: "buy", IsToken2022: false, IsMayhemMode: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: args.TokenAmount, Token1AmountUint64: args.SolAmount, }, nil } -func parseBonkInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { - msg := tx.Message - if instructionIndex >= len(msg.Instructions) { - return nil, fmt.Errorf("instruction index out of bounds") - } +func parseBonkInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { - instruction := msg.Instructions[instructionIndex] - if len(instruction.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("data is empty") } - if matchMethod(instruction.Data, bonkBuyAndSellTokensIX) { - return parseBonkBuyAndSell(tx, &instruction) + if matchMethod(data, bonkBuyAndSellTokensIX) { + return parseBonkBuyAndSell(tx, accounts, data) } return nil, nil } -func parseBonkBuyAndSell(tx *versionedTransaction, instruction *compiledInstruction) (*TxSignal, error) { - if len(instruction.Accounts) < 8 { +func parseBonkBuyAndSell(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) { + if len(accounts) < 8 { return nil, fmt.Errorf("accounts too short") } - staticKeys := tx.Message.StaticAccountKeys - programId, err := getStaticKey(staticKeys, int(instruction.Accounts[7])) + programId, err := tx.GetAccount(accounts[7]) //getStaticKey(staticKeys, int(instruction.Accounts[7])) if err != nil { return nil, err } @@ -1741,27 +1831,27 @@ func parseBonkBuyAndSell(tx *versionedTransaction, instruction *compiledInstruct return nil, nil } - user, err := getStaticKey(staticKeys, int(instruction.Accounts[0])) + user, err := tx.GetAccount(accounts[0]) //getStaticKey(staticKeys, int(instruction.Accounts[0])) if err != nil { return nil, err } - flagAccount, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) + flagAccount, err := tx.GetAccount(accounts[4]) //getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } - amount1 := binary.LittleEndian.Uint64(instruction.Data[17:25]) - amount2 := binary.LittleEndian.Uint64(instruction.Data[25:33]) + amount1 := binary.LittleEndian.Uint64(data[17:25]) + amount2 := binary.LittleEndian.Uint64(data[25:33]) if user == flagAccount { - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[6])) + mint, err := tx.GetAccount(accounts[6]) // getStaticKey(staticKeys, int(instruction.Accounts[6])) if err != nil { return nil, err } return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "bonk", Maker: user.String(), Token0Address: mint.String(), @@ -1773,18 +1863,18 @@ func parseBonkBuyAndSell(tx *versionedTransaction, instruction *compiledInstruct IsToken2022: false, IsMayhemMode: false, ExactSOL: true, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: amount2, Token1AmountUint64: amount1, }, nil } else { - mint, err := getStaticKey(staticKeys, int(instruction.Accounts[5])) + mint, err := tx.GetAccount(accounts[5]) //getStaticKey(staticKeys, int(instruction.Accounts[5])) if err != nil { return nil, err } return &TxSignal{ - TxHash: tx.Signatures[0].String(), + TxHash: tx.Signatures(), Label: "bonk", Maker: user.String(), Token0Address: mint.String(), @@ -1796,7 +1886,7 @@ func parseBonkBuyAndSell(tx *versionedTransaction, instruction *compiledInstruct IsToken2022: false, IsMayhemMode: false, ExactSOL: false, - Block: tx.Block, + Block: tx.Block(), Token0AmountUint64: amount1, Token1AmountUint64: amount2, }, nil diff --git a/pkg/shreder/txparser_test.go b/pkg/shreder/txparser_test.go index a19589a..2368664 100644 --- a/pkg/shreder/txparser_test.go +++ b/pkg/shreder/txparser_test.go @@ -149,16 +149,16 @@ func TestParseTermBuy(t *testing.T) { } client := rpc.New(rpcUrl) - signals := ParseTransaction( - getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"), - nil, - false, - ) - if len(signals) != 1 { - t.Fatalf("expected 1 signal, got %d", len(signals)) - } + txChannel := make(chan TxSignal, 1) + go func() { + ParseTransaction( + getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"), + nil, txChannel, + false, + ) + }() - signal := signals[0] + signal := <-txChannel if signal.Label != "terminal" { t.Fatalf("expected terminal signal, got %s", signal.Label) } @@ -186,16 +186,16 @@ func TestParseBonkBuy(t *testing.T) { } client := rpc.New(rpcUrl) - signals := ParseTransaction( - getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"), - nil, - false, - ) - if len(signals) != 1 { - t.Fatalf("expected 1 signal, got %d", len(signals)) - } + txChannel := make(chan TxSignal, 1) + go func() { + ParseTransaction( + getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"), + nil, txChannel, + false, + ) + }() - signal := signals[0] + signal := <-txChannel if signal.Label != "bonk" { t.Fatalf("expected bonk signal, got %s", signal.Label) } @@ -223,16 +223,16 @@ func TestParseBonkSell(t *testing.T) { } client := rpc.New(rpcUrl) - signals := ParseTransaction( - getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"), - nil, - false, - ) - if len(signals) != 1 { - t.Fatalf("expected 1 signal, got %d", len(signals)) - } + txChannel := make(chan TxSignal, 1) + go func() { + ParseTransaction( + getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"), + nil, txChannel, + false, + ) + }() - signal := signals[0] + signal := <-txChannel if signal.Label != "bonk" { t.Fatalf("expected bonk signal, got %s", signal.Label) } @@ -260,16 +260,16 @@ func TestParsePhotonBuy(t *testing.T) { } client := rpc.New(rpcUrl) - signals := ParseTransaction( - getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"), - nil, - false, - ) - if len(signals) != 1 { - t.Fatalf("expected 1 signal, got %d", len(signals)) - } + txChannel := make(chan TxSignal, 1) + go func() { + ParseTransaction( + getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"), + nil, txChannel, + false, + ) + }() - signal := signals[0] + signal := <-txChannel if signal.Label != "photon" { t.Fatalf("expected terminal signal, got %s", signal.Label) }