7 Commits
test ... 1.x

Author SHA1 Message Date
bijianing97
35c5c83f4b Add dflow pumpfun parse 2026-01-27 14:48:18 +08:00
bijianing97
5f97972194 Add jupiter pumpamm buy pase 2026-01-26 17:18:29 +08:00
bijianing97
741d333e1b Update juptier pumpfun usdc usdt usd1 filter 2026-01-23 15:07:03 +08:00
bijianing97
594c46a1d2 Add bloomrouter pumpfun parse 2026-01-22 17:50:26 +08:00
bijianing97
45107aa8c3 Add JupiterAggregatorV6 pumpfun parse 2026-01-22 17:10:13 +08:00
bijianing97
36db4729d4 Update metora dlmm program parse 2026-01-22 14:32:45 +08:00
23f37cff2c chore: add release pool 2026-01-19 09:35:47 +08:00
17 changed files with 12764 additions and 1272 deletions

62
cmd/debug_jupv6/main.go Normal file
View File

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

180
cmd/txparse/main.go Normal file
View File

@@ -0,0 +1,180 @@
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,10 +4,13 @@ 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
@@ -19,10 +22,8 @@ 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
@@ -32,7 +33,6 @@ 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,9 +88,8 @@ 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=
@@ -118,6 +117,8 @@ 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,6 +59,7 @@ 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
} }
@@ -92,11 +93,10 @@ 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 FillableTransaction, tablePubkey solana.PublicKey, idx []uint8) bool { func (at *AddressTables) FillToTx(tx *versionedTransaction, 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 FillableTransaction, tablePubkey solana.Pub
} }
return false return false
} }
tx.FillLookupTable(addresses.addresses[i]) tx.Message.StaticAccountKeys = append(tx.Message.StaticAccountKeys, 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(true)) pool, err := ants.NewPool(poolSize, ants.WithNonblocking(false))
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
@@ -113,42 +113,7 @@ func (c *Client) Wait() {
logger.Debug("shreder client stopped") logger.Debug("shreder client stopped")
} }
func (c *Client) ReadEntriesSync(ctx context.Context, txCh chan<- TxSignal) error { func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) 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
@@ -163,10 +128,14 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
return err return err
} }
// reboot the pool
c.pool.Reboot()
for { for {
response, err := stream.Recv() var response *SubscribeTransactionsResponse
response, err = stream.Recv()
if err != nil { if err != nil {
return err break
} }
if c.enableBlockStats { if c.enableBlockStats {
@@ -184,11 +153,28 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
txData := response.Transaction txData := response.Transaction
err = c.pool.Submit(func() { err = c.pool.Submit(func() {
ParseTransaction(txData, c.tableLoader, txCh, c.enableParseStats) txBatch := ParseTransaction(txData, c.tableLoader, c.enableParseStats)
}) if len(txBatch) == 0 {
if err != nil { return
logger.Error("failed to submit transaction: ", "err", err)
} }
for _, tx := range txBatch {
tx.Source = "shreder"
}
select {
case <-ctx.Done():
return
case txCh <- txBatch:
}
})
if err != nil {
break
} }
} }
// sync waiting for all tasks to complete
c.pool.Release()
return err
}

View File

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

File diff suppressed because one or more lines are too long

8471
pkg/shreder/dlmm_idl.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,334 +0,0 @@
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
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -48,11 +48,19 @@ 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:"-"`
Stats Stats `json:"-"` ParseStart time.Time `json:"parse_start"`
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)
txChannel := make(chan TxSignal, 1) signals := ParseTransaction(
go func() {
ParseTransaction(
getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"), getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"),
nil, txChannel, nil,
false, false,
) )
}() if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := <-txChannel signal := signals[0]
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)
txChannel := make(chan TxSignal, 1) signals := ParseTransaction(
go func() {
ParseTransaction(
getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"), getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"),
nil, txChannel, nil,
false, false,
) )
}() if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := <-txChannel signal := signals[0]
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)
txChannel := make(chan TxSignal, 1) signals := ParseTransaction(
go func() {
ParseTransaction(
getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"), getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"),
nil, txChannel, nil,
false, false,
) )
}() if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := <-txChannel signal := signals[0]
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)
txChannel := make(chan TxSignal, 1) signals := ParseTransaction(
go func() {
ParseTransaction(
getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"), getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"),
nil, txChannel, nil,
false, false,
) )
}() if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals))
}
signal := <-txChannel signal := signals[0]
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,3 +289,83 @@ 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")
}
}