1 Commits
1.x ... test

Author SHA1 Message Date
thloyi
9f73e8f57f pipo tx parse 2026-01-23 17:58:59 +08:00
17 changed files with 1273 additions and 12765 deletions

View File

@@ -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)
}

View File

@@ -8,8 +8,10 @@ import (
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time"
"github.com/gagliardetto/solana-go/rpc" "github.com/gagliardetto/solana-go/rpc"
"github.com/shopspring/decimal"
"github.com/samlior/libsam/pkg/shreder" "github.com/samlior/libsam/pkg/shreder"
) )
@@ -76,9 +78,9 @@ func main() {
cancel() cancel()
}() }()
// async read from shreder // async read from shreder
txCh := make(chan shreder.TxSignalBatch, 1000) txCh := make(chan shreder.TxSignal, 1000)
go func() { go func() {
err := shrederClient.ReadSync(ctx, txCh) err := shrederClient.ReadEntriesSync(ctx, txCh)
if err != nil { if err != nil {
if !errors.Is(err, context.Canceled) { if !errors.Is(err, context.Canceled) {
panic(err) panic(err)
@@ -90,13 +92,20 @@ func main() {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case txBatch := <-txCh: case tx := <-txCh:
//jsonData, _ := json.MarshalIndent(txBatch, "", " ") //jsonData, _ := json.MarshalIndent(txBatch, "", " ")
for _, tx := range txBatch { if tx.Token0Amount.GreaterThan(decimal.NewFromInt(100)) && (tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow") {
if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" { fmt.Println(time.Now(), "===============", tx.TxHash,
fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart)) "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) //fmt.Println(txBatch[0].TxHash)
} }
} }

View File

@@ -1,180 +0,0 @@
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/programs/address-lookup-table"
"github.com/gagliardetto/solana-go/rpc"
"github.com/samlior/libsam/pkg/shreder"
)
const (
rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
txSignature = "4YUQzsQcHxt5jA6qKPVBWCgw8VRuE6bZqAoXeiwptbdLwta3QnDbWHzjwP3mY8hJPPerSf1yGbpdL2SdyWZTJ9e1"
labelFilter = ""
enableStats = true
)
func main() {
if rpcURL == "" || rpcURL == "REPLACE_WITH_RPC_URL" {
log.Fatal("rpcURL is not set in cmd/dlmmparse/main.go")
}
if txSignature == "" || txSignature == "REPLACE_WITH_TX_SIGNATURE" {
log.Fatal("txSignature is not set in cmd/dlmmparse/main.go")
}
client := rpc.New(rpcURL)
sig, err := solana.SignatureFromBase58(txSignature)
if err != nil {
log.Fatalf("invalid txSignature: %v", err)
}
version := uint64(0)
tx, err := client.GetTransaction(
context.Background(),
sig,
&rpc.GetTransactionOpts{
Commitment: rpc.CommitmentFinalized,
MaxSupportedTransactionVersion: &version,
},
)
if err != nil {
log.Fatalf("getTransaction failed: %v", err)
}
if tx == nil || tx.Transaction == nil {
log.Fatal("transaction is empty")
}
rawTx, err := tx.Transaction.GetTransaction()
if err != nil {
log.Fatalf("decode transaction failed: %v", err)
}
if rawTx == nil {
log.Fatal("decoded transaction is nil")
}
if len(rawTx.Message.AddressTableLookups) > 0 {
tables := make(map[solana.PublicKey]solana.PublicKeySlice, len(rawTx.Message.AddressTableLookups))
for _, lookup := range rawTx.Message.AddressTableLookups {
state, err := addresslookuptable.GetAddressLookupTable(context.Background(), client, lookup.AccountKey)
if err != nil {
log.Fatalf("load address table %s failed: %v", lookup.AccountKey, err)
}
tables[lookup.AccountKey] = state.Addresses
}
if err := rawTx.Message.SetAddressTables(tables); err != nil {
log.Fatalf("set address tables failed: %v", err)
}
if err := rawTx.Message.ResolveLookups(); err != nil {
log.Fatalf("resolve address lookups failed: %v", err)
}
}
update := toSubscribeUpdate(tx.Slot, rawTx)
signals := shreder.ParseTransaction(update, nil, enableStats)
if len(signals) == 0 {
fmt.Println("no signals parsed")
return
}
printed := false
for _, signal := range signals {
if signal == nil {
continue
}
if labelFilter != "" && signal.Label != labelFilter {
continue
}
printed = true
output, err := json.MarshalIndent(signal, "", " ")
if err != nil {
log.Fatalf("marshal signal failed: %v", err)
}
fmt.Println(string(output))
}
if printed {
return
}
if labelFilter != "" {
fmt.Printf("no %s signal parsed, dump all signals:\n", labelFilter)
} else {
fmt.Println("no matching signal parsed, dump all signals:")
}
for _, signal := range signals {
if signal == nil {
continue
}
output, err := json.MarshalIndent(signal, "", " ")
if err != nil {
log.Fatalf("marshal signal failed: %v", err)
}
fmt.Println(string(output))
}
}
func toSubscribeUpdate(slot uint64, tx *solana.Transaction) *shreder.SubscribeUpdateTransaction {
signatures := make([][]byte, len(tx.Signatures))
for i, sig := range tx.Signatures {
signatures[i] = sig[:]
}
accountKeys := make([][]byte, len(tx.Message.AccountKeys))
for i, key := range tx.Message.AccountKeys {
accountKeys[i] = key[:]
}
instructions := make([]*shreder.CompiledInstruction, len(tx.Message.Instructions))
for i, instr := range tx.Message.Instructions {
accounts := make([]byte, len(instr.Accounts))
for j, acc := range instr.Accounts {
accounts[j] = byte(acc)
}
instructions[i] = &shreder.CompiledInstruction{
ProgramIdIndex: uint32(instr.ProgramIDIndex),
Accounts: accounts,
Data: instr.Data[:],
}
}
addressTableLookups := make([]*shreder.MessageAddressTableLookup, len(tx.Message.AddressTableLookups))
for i, lookup := range tx.Message.AddressTableLookups {
writable := make([]byte, len(lookup.WritableIndexes))
for j, idx := range lookup.WritableIndexes {
writable[j] = byte(idx)
}
readonly := make([]byte, len(lookup.ReadonlyIndexes))
for j, idx := range lookup.ReadonlyIndexes {
readonly[j] = byte(idx)
}
addressTableLookups[i] = &shreder.MessageAddressTableLookup{
AccountKey: lookup.AccountKey[:],
WritableIndexes: writable,
ReadonlyIndexes: readonly,
}
}
return &shreder.SubscribeUpdateTransaction{
Transaction: &shreder.Transaction{
Signatures: signatures,
Message: &shreder.Message{
Header: &shreder.MessageHeader{
NumRequiredSignatures: uint32(tx.Message.Header.NumRequiredSignatures),
NumReadonlySignedAccounts: uint32(tx.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: uint32(tx.Message.Header.NumReadonlyUnsignedAccounts),
},
AccountKeys: accountKeys,
RecentBlockhash: nil,
Instructions: instructions,
Versioned: false,
AddressTableLookups: addressTableLookups,
},
},
Slot: slot,
}
}

6
go.mod
View File

@@ -4,13 +4,10 @@ go 1.25.1
require ( require (
github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250722092120-44561cb37455 github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250722092120-44561cb37455
github.com/gagliardetto/binary v0.8.0
github.com/gagliardetto/solana-go v1.12.0 github.com/gagliardetto/solana-go v1.12.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/mr-tron/base58 v1.2.0 github.com/mr-tron/base58 v1.2.0
github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454 github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454
github.com/panjf2000/ants/v2 v2.11.4 github.com/panjf2000/ants/v2 v2.11.4
github.com/quic-go/quic-go v0.58.0
github.com/shopspring/decimal v1.4.0 github.com/shopspring/decimal v1.4.0
google.golang.org/grpc v1.75.0 google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.10 google.golang.org/protobuf v1.36.10
@@ -22,8 +19,10 @@ require (
github.com/blendle/zapdriver v1.3.1 // indirect github.com/blendle/zapdriver v1.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.9.0 // indirect github.com/fatih/color v1.9.0 // indirect
github.com/gagliardetto/binary v0.8.0 // indirect
github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect github.com/klauspost/compress v1.13.6 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
@@ -33,6 +32,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
github.com/quic-go/quic-go v0.58.0 // indirect
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect
go.mongodb.org/mongo-driver v1.12.2 // indirect go.mongodb.org/mongo-driver v1.12.2 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect

5
go.sum
View File

@@ -88,8 +88,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -117,8 +118,6 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=

View File

@@ -59,7 +59,6 @@ func (at *AddressTables) loadAddressTable(tablePubkey solana.PublicKey) ([]solan
addresses = append(addresses, solana.PublicKeyFromBytes(data[offset:offset+32])) addresses = append(addresses, solana.PublicKeyFromBytes(data[offset:offset+32]))
offset += 32 offset += 32
} }
// addresses = append(addresses, solana.PublicKeyFromBytes(data[start:start+32]))
return addresses, nil return addresses, nil
} }
@@ -93,10 +92,11 @@ func (at *AddressTables) load(tablePubkey solana.PublicKey) {
total := at.tables.Len() total := at.tables.Len()
logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total) 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) addresses, ok := at.tables.Get(tablePubkey)
if !ok { if !ok {
at.load(tablePubkey) at.load(tablePubkey)
@@ -112,7 +112,7 @@ func (at *AddressTables) FillToTx(tx *versionedTransaction, tablePubkey solana.P
} }
return false return false
} }
tx.Message.StaticAccountKeys = append(tx.Message.StaticAccountKeys, addresses.addresses[i]) tx.FillLookupTable(addresses.addresses[i])
} }
return true return true
} }

View File

@@ -70,7 +70,7 @@ func NewShrederClient(
poolSize := runtime.NumCPU()*2 + 2 poolSize := runtime.NumCPU()*2 + 2
logger.Info("creating shreder client", "url", url, "pool_size", poolSize) 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 { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
@@ -113,7 +113,42 @@ func (c *Client) Wait() {
logger.Debug("shreder client stopped") 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) stream, err := c.client.SubscribeTransactions(ctx)
if err != nil { if err != nil {
return err return err
@@ -128,14 +163,10 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error
return err return err
} }
// reboot the pool
c.pool.Reboot()
for { for {
var response *SubscribeTransactionsResponse response, err := stream.Recv()
response, err = stream.Recv()
if err != nil { if err != nil {
break return err
} }
if c.enableBlockStats { if c.enableBlockStats {
@@ -153,28 +184,11 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error
txData := response.Transaction txData := response.Transaction
err = c.pool.Submit(func() { err = c.pool.Submit(func() {
txBatch := ParseTransaction(txData, c.tableLoader, c.enableParseStats) ParseTransaction(txData, c.tableLoader, txCh, c.enableParseStats)
if len(txBatch) == 0 {
return
}
for _, tx := range txBatch {
tx.Source = "shreder"
}
select {
case <-ctx.Done():
return
case txCh <- txBatch:
}
}) })
if err != nil { if err != nil {
break logger.Error("failed to submit transaction: ", "err", err)
} }
} }
// sync waiting for all tasks to complete
c.pool.Release()
return err
} }

View File

@@ -86,28 +86,28 @@ const (
drv1Nexus drv1Nexus
) )
// PumpFun*Options { amount: u64, orchestrator_flags: OrchestratorFlags{flags u8} } // PumpFunAmmSellOptions { amount: u64, orchestrator_flags: OrchestratorFlags{flags u8} }
type pumpFunAction struct { type pumpFunAmm struct {
Amount uint64 Amount uint64
Flags uint8 Flags uint8
} }
type dflowAction struct { type dflowAction struct {
Tag uint8 Tag uint8
Pump *pumpFunAction Pump *pumpFunAmm
} }
type dflowSwapParams struct { type dflowSwapParams struct {
Actions []dflowAction Actions []dflowAction
} }
// bytes to skip for Action variants; only PumpFun* actions are decoded. // bytes to skip for Action variants before/after PumpFunAmmSell; only PumpFunAmmSell is decoded.
func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) { func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAmm, error) {
switch tag { switch tag {
case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2: case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2:
// amount u64 + bool + orchestrator_flags u8 // amount u64 + bool + orchestrator_flags u8
return nil, dec.SkipBytes(8 + 1 + 1) return nil, dec.SkipBytes(8 + 1 + 1)
case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap, case ActRaydiumAmmSwap, ActLifinityV2Swap, ActPumpFunBuy, ActPumpFunSell, ActObricV2Swap,
ActSolFiSwap, ActRubiconSwap, ActMeteoraDammV1Swap, ActRaydiumCpSwap, ActSolFiSwap, ActRubiconSwap, ActMeteoraDammV1Swap, ActRaydiumCpSwap,
ActStabbleStableSwap, ActTesseraVSwap, ActMeteoraDammV2Swap, ActRaydiumLaunchlabSwap, ActStabbleStableSwap, ActTesseraVSwap, ActMeteoraDammV2Swap, ActRaydiumLaunchlabSwap,
ActZeroFiSwap, ActAlphaQSwap, ActTokenSwap, ActSolFiV2Swap, ActMozartSwap, ActHeavenSwap, ActZeroFiSwap, ActAlphaQSwap, ActTokenSwap, ActSolFiV2Swap, ActMozartSwap, ActHeavenSwap,
@@ -123,7 +123,7 @@ func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
case ActGammaSwap: case ActGammaSwap:
// amount u64 + endorsed bool + orchestrator_flags u8 // amount u64 + endorsed bool + orchestrator_flags u8
return nil, dec.SkipBytes(8 + 1 + 1) return nil, dec.SkipBytes(8 + 1 + 1)
case ActPumpFunAmmSell, ActPumpFunAmmBuy, ActPumpFunBuy, ActPumpFunSell: case ActPumpFunAmmSell, ActPumpFunAmmBuy:
amt, err := dec.ReadUint64(binary.LittleEndian) amt, err := dec.ReadUint64(binary.LittleEndian)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -132,7 +132,7 @@ func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &pumpFunAction{Amount: amt, Flags: flg}, nil return &pumpFunAmm{Amount: amt, Flags: flg}, nil
case ActMeteoraDbcSwap: case ActMeteoraDbcSwap:
// amount u64 + is_rate_limiter_applied bool + orchestrator_flags u8 // amount u64 + is_rate_limiter_applied bool + orchestrator_flags u8
return nil, dec.SkipBytes(8 + 1 + 1) return nil, dec.SkipBytes(8 + 1 + 1)
@@ -232,46 +232,14 @@ func decodeSwap2Params(data []byte) (*dflowSwapParams, error) {
return out, nil return out, nil
} }
func findDflowPumpAmmMints(staticKeys []solana.PublicKey, accounts []uint8) (solana.PublicKey, solana.PublicKey, bool, error) { func parseDFlowInstruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
for i, acctIdx := range accounts { if len(data) < 8 {
key, err := getStaticKey(staticKeys, int(acctIdx))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, false, err
}
if !key.Equals(pumpAmmProgramID) {
continue
}
baseIdx := i + 4
quoteIdx := i + 5
if baseIdx >= len(accounts) || quoteIdx >= len(accounts) {
return solana.PublicKey{}, solana.PublicKey{}, false, nil
}
baseMint, err := getStaticKey(staticKeys, int(accounts[baseIdx]))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, false, err
}
quoteMint, err := getStaticKey(staticKeys, int(accounts[quoteIdx]))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, false, err
}
return baseMint, quoteMint, true, nil
}
return solana.PublicKey{}, solana.PublicKey{}, false, nil
}
func parseDFlowInstruction(tx *versionedTransaction, instructionIndex int) (TxSignalBatch, 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 {
return nil, nil return nil, nil
} }
var err error var err error
disc := ix.Data[:8] disc := data[:8]
payload := ix.Data[8:] payload := data[8:]
var params *dflowSwapParams var params *dflowSwapParams
switch { switch {
@@ -289,121 +257,65 @@ func parseDFlowInstruction(tx *versionedTransaction, instructionIndex int) (TxSi
return nil, nil return nil, nil
} }
var ( var pump *pumpFunAmm
pumpAmmBuy *pumpFunAction
pumpAmmSell *pumpFunAction
pumpBuy *pumpFunAction
pumpSell *pumpFunAction
)
for _, act := range params.Actions { for _, act := range params.Actions {
if act.Pump == nil { if act.Tag == ActPumpFunAmmSell && act.Pump != nil {
continue pump = act.Pump
} break
switch act.Tag {
case ActPumpFunAmmSell:
pumpAmmSell = act.Pump
case ActPumpFunAmmBuy:
pumpAmmBuy = act.Pump
case ActPumpFunBuy:
pumpBuy = act.Pump
case ActPumpFunSell:
pumpSell = act.Pump
} }
} }
if pump == nil {
out := make(TxSignalBatch, 0, 2) return nil, nil // only care about PumpFunAmmSell
if pumpAmmSell != nil || pumpAmmBuy != nil {
event := "sell"
amt := pumpAmmSell
isBuy := false
if amt == nil {
event = "buy"
isBuy = true
amt = pumpAmmBuy
}
baseMint, quoteMint, ok, err := findDflowPumpAmmMints(tx.Message.StaticAccountKeys, ix.Accounts)
if err != nil {
return nil, err
}
if ok && quoteMint.Equals(solana.WrappedSol) {
var (
token0Amount decimal.Decimal
token1Amount decimal.Decimal
token0AmountUint64 uint64
token1AmountUint64 uint64
exactSol bool
)
if isBuy {
exactSol = true
token1Amount = formatSolAmount(amt.Amount)
token1AmountUint64 = amt.Amount
} else {
token0Amount = formatTokenAmount(amt.Amount)
token0AmountUint64 = amt.Amount
}
out = append(out, &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Program: "PumpAMM",
Event: event,
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: token0Amount,
Token1Amount: token1Amount,
ExactSOL: exactSol,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64,
})
}
} }
if pumpSell != nil || pumpBuy != nil { // Require WSOL pair when destination mint is provided.
event := "sell" var (
amt := pumpSell srcIdx uint8
isBuy := false )
if amt == nil { if len(accounts) <= 6 {
event = "buy"
isBuy = true
amt = pumpBuy
}
mint, ok, err := findPumpFunMint(tx.Message.StaticAccountKeys, ix.Accounts)
if err != nil {
return nil, err
}
if ok {
var (
token0Amount decimal.Decimal
token1Amount decimal.Decimal
token0AmountUint64 uint64
token1AmountUint64 uint64
exactSol bool
)
if isBuy {
exactSol = true
token1Amount = formatSolAmount(amt.Amount)
token1AmountUint64 = amt.Amount
} else {
token0Amount = formatTokenAmount(amt.Amount)
token0AmountUint64 = amt.Amount
}
out = append(out, &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Program: "Pump",
Event: event,
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: token0Amount,
Token1Amount: token1Amount,
ExactSOL: exactSol,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64,
})
}
}
if len(out) == 0 {
return nil, nil return nil, nil
} }
return out, nil accounts = accounts[5:]
for i, acctIdx := range accounts {
key, err := tx.GetAccount(acctIdx) //getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx))
if err != nil {
return nil, err
}
if key.Equals(pumpAmmProgramID) {
srcIdx = uint8(i + 4)
break
}
}
if srcIdx == 0 || srcIdx+1 >= uint8(len(accounts)) {
return nil, nil
}
baseMint, err := tx.GetAccount(accounts[srcIdx]) // getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx]))
if err != nil {
return nil, err
}
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(),
Maker: maker.String(),
Program: "PumpAMM",
Event: "sell",
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(pump.Amount),
Token1Amount: decimal.Zero,
Token0AmountUint64: pump.Amount,
Block: tx.Block(),
Token1AmountUint64: 0,
}
return sig, nil
} }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

334
pkg/shreder/entry.go Normal file
View File

@@ -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
}

41
pkg/shreder/entry_test.go Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -245,17 +245,13 @@ type OkxV2SwapScorch struct {
Id [16]byte Id [16]byte
} }
func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { func parseOkxDexRouteV2Instruction(tx TransactionGetter, accounts []uint8, data []byte) (*TxSignal, error) {
msg := tx.Message
if instructionIndex >= len(msg.Instructions) { if len(data) < 8 {
return nil, fmt.Errorf("instruction index out of bounds")
}
ix := msg.Instructions[instructionIndex]
if len(ix.Data) < 8 {
return nil, nil return nil, nil
} }
disc := ix.Data[:8] disc := data[:8]
data := ix.Data[8:] data = data[8:]
var ( var (
args *OkxV2SwapArgs args *OkxV2SwapArgs
@@ -288,8 +284,8 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
default: default:
return nil, nil return nil, nil
} }
if len(ix.Accounts) < 15 { if len(accounts) < 15 {
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts)) return nil, fmt.Errorf("invalid account count: %d", len(accounts))
} }
var ( var (
inputAmount uint64 inputAmount uint64
@@ -303,24 +299,24 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
} }
} }
if routeCount > 1 { 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 return nil, nil
} }
if inputAmount == 0 { if inputAmount == 0 {
return nil, nil 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 ( var (
srcIdx uint8 srcIdx uint8
) )
if len(ix.Accounts) <= 15 { if len(accounts) <= 15 {
return nil, nil return nil, nil
} }
accounts := ix.Accounts[14:] accounts = accounts[14:]
for i, acctIdx := range accounts { 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 { if err != nil {
return nil, err return nil, err
} }
@@ -333,7 +329,7 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
return nil, nil 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 { if err != nil {
return nil, err return nil, err
} }
@@ -341,17 +337,18 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
return nil, nil 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 { if err != nil {
return nil, err return nil, err
} }
if !quoteMint.Equals(solana.WrappedSol) { if !quoteMint.Equals(solana.WrappedSol) {
return nil, nil return nil, nil
} }
maker, _ := tx.GetAccount(0)
return &TxSignal{ return &TxSignal{
TxHash: tx.Signatures[0].String(), TxHash: tx.Signatures(),
Maker: tx.Message.StaticAccountKeys[0].String(), Maker: maker.String(),
Token0Address: baseMint.String(), Token0Address: baseMint.String(),
Token1Address: wsolMint, Token1Address: wsolMint,
Token0Amount: formatTokenAmount(inputAmount), Token0Amount: formatTokenAmount(inputAmount),
@@ -363,6 +360,7 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
IsMayhemMode: false, IsMayhemMode: false,
ExactSOL: false, ExactSOL: false,
Token0AmountUint64: inputAmount, Token0AmountUint64: inputAmount,
Block: tx.Block(),
Token1AmountUint64: 0, Token1AmountUint64: 0,
}, nil }, nil
} }

View File

@@ -48,19 +48,11 @@ type TxSignal struct {
ExactSOL bool `json:"exact_in"` ExactSOL bool `json:"exact_in"`
//Just for metaora DLMM
// ActiveBin is the active bin id provided by swap_with_price_impact(2).
ActiveBin int32 `json:"active_bin"`
// MaxPriceImpactBps is the price impact guard for swap_with_price_impact(2).
MaxPriceImpactBps uint16 `json:"max_price_impact_bps"`
// parsed values // parsed values
Token0AmountUint64 uint64 `json:"-"` Token0AmountUint64 uint64 `json:"-"`
Token1AmountUint64 uint64 `json:"-"` Token1AmountUint64 uint64 `json:"-"`
ParseStart time.Time `json:"parse_start"` Stats Stats `json:"-"`
ParseEnd time.Time `json:"parse_end"`
} }
func (t *TxSignal) Parse() *TxSignal { func (t *TxSignal) Parse() *TxSignal {

File diff suppressed because it is too large Load Diff

View File

@@ -149,16 +149,16 @@ func TestParseTermBuy(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction( txChannel := make(chan TxSignal, 1)
getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"), go func() {
nil, ParseTransaction(
false, getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"),
) nil, txChannel,
if len(signals) != 1 { false,
t.Fatalf("expected 1 signal, got %d", len(signals)) )
} }()
signal := signals[0] signal := <-txChannel
if signal.Label != "terminal" { if signal.Label != "terminal" {
t.Fatalf("expected terminal signal, got %s", signal.Label) t.Fatalf("expected terminal signal, got %s", signal.Label)
} }
@@ -186,16 +186,16 @@ func TestParseBonkBuy(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction( txChannel := make(chan TxSignal, 1)
getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"), go func() {
nil, ParseTransaction(
false, getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"),
) nil, txChannel,
if len(signals) != 1 { false,
t.Fatalf("expected 1 signal, got %d", len(signals)) )
} }()
signal := signals[0] signal := <-txChannel
if signal.Label != "bonk" { if signal.Label != "bonk" {
t.Fatalf("expected bonk signal, got %s", signal.Label) t.Fatalf("expected bonk signal, got %s", signal.Label)
} }
@@ -223,16 +223,16 @@ func TestParseBonkSell(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction( txChannel := make(chan TxSignal, 1)
getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"), go func() {
nil, ParseTransaction(
false, getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"),
) nil, txChannel,
if len(signals) != 1 { false,
t.Fatalf("expected 1 signal, got %d", len(signals)) )
} }()
signal := signals[0] signal := <-txChannel
if signal.Label != "bonk" { if signal.Label != "bonk" {
t.Fatalf("expected bonk signal, got %s", signal.Label) t.Fatalf("expected bonk signal, got %s", signal.Label)
} }
@@ -260,16 +260,16 @@ func TestParsePhotonBuy(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction( txChannel := make(chan TxSignal, 1)
getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"), go func() {
nil, ParseTransaction(
false, getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"),
) nil, txChannel,
if len(signals) != 1 { false,
t.Fatalf("expected 1 signal, got %d", len(signals)) )
} }()
signal := signals[0] signal := <-txChannel
if signal.Label != "photon" { if signal.Label != "photon" {
t.Fatalf("expected terminal signal, got %s", signal.Label) t.Fatalf("expected terminal signal, got %s", signal.Label)
} }
@@ -289,83 +289,3 @@ func TestParsePhotonBuy(t *testing.T) {
t.Fatalf("expected token1 amount 1955555553, got %d", signal.Token1AmountUint64) t.Fatalf("expected token1 amount 1955555553, got %d", signal.Token1AmountUint64)
} }
} }
func TestParseJupiterV6PumpFunBuy(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "4QF5whXwjx234fMXeH3HrJCy5knFJmKPtgbXys8xKGz1pZypqPvXBr4BoAqXfYn8jLL4HXPY1pcvxCCW1XREFNxd"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "jupiterv6" {
t.Fatalf("expected jupiterv6 signal, got %s", signal.Label)
}
if signal.Event != "buy" {
t.Fatalf("expected buy event, got %s", signal.Event)
}
if signal.Maker != "92ySgsZs3rsrUAq2aeEqYacXQQGmz6e4xHPrRGxLDJXb" {
t.Fatalf("expected maker 92ySgsZs3rsrUAq2aeEqYacXQQGmz6e4xHPrRGxLDJXb, got %s", signal.Maker)
}
if signal.Token0Address != "5kSWidFwDKPZiNf52TfincpVn8ufvkAfEzZ9pk8Dpump" {
t.Fatalf("expected token0 address 5kSWidFwDKPZiNf52TfincpVn8ufvkAfEzZ9pk8Dpump, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 2410530637576 {
t.Fatalf("expected token0 amount 2410530637576, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 380000000 {
t.Fatalf("expected token1 amount 380000000, got %d", signal.Token1AmountUint64)
}
if !signal.ExactSOL {
t.Fatalf("expected ExactSOL true, got false")
}
}
func TestParseJupiterV6PumpFunSell(t *testing.T) {
rpcUrl := os.Getenv("SOL_RPC_URL")
if rpcUrl == "" {
t.Fatalf("SOL_RPC_URL is not set")
}
client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "yCnE7ZA8dqB5iAZtwpSN2ar5HXh3gBjgaG2xtnwXDPFyHAm5XFU8642uTZTH5A2iPQ6G9hrj5eEPAJiWrfe38gM"),
nil,
false,
)
if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := signals[0]
if signal.Label != "jupiterv6" {
t.Fatalf("expected jupiterv6 signal, got %s", signal.Label)
}
if signal.Event != "sell" {
t.Fatalf("expected sell event, got %s", signal.Event)
}
if signal.Maker != "CGfWcKKcVQNBCL1vpxXdg6rvfYpQmnS3WkyA22Lk5XnZ" {
t.Fatalf("expected maker CGfWcKKcVQNBCL1vpxXdg6rvfYpQmnS3WkyA22Lk5XnZ, got %s", signal.Maker)
}
if signal.Token0Address != "wp8Mwxy7btAD9hNWsfJyoPNJnjXS9fuNG4mnhQZpump" {
t.Fatalf("expected token0 address wp8Mwxy7btAD9hNWsfJyoPNJnjXS9fuNG4mnhQZpump, got %s", signal.Token0Address)
}
if signal.Token0AmountUint64 != 127531720509990 {
t.Fatalf("expected token0 amount 127531720509990, got %d", signal.Token0AmountUint64)
}
if signal.Token1AmountUint64 != 5296451290 {
t.Fatalf("expected token1 amount 5296451290, got %d", signal.Token1AmountUint64)
}
if signal.ExactSOL {
t.Fatalf("expected ExactSOL false, got true")
}
}