8 Commits

Author SHA1 Message Date
bijianing97
519b0ebb0b fix sfng error 2026-01-28 15:24:43 +08:00
thloyi
be86c888eb split program source file 2026-01-28 15:02:54 +08:00
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
35 changed files with 14414 additions and 2004 deletions

View File

@@ -10,9 +10,9 @@ import (
"github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/programs/system" "github.com/gagliardetto/solana-go/programs/system"
"github.com/gagliardetto/solana-go/rpc" "github.com/gagliardetto/solana-go/rpc"
"github.com/samlior/libsam/pkg/consts" "github.com/samlior/libsam/v2/pkg/consts"
"github.com/samlior/libsam/pkg/enum" "github.com/samlior/libsam/v2/pkg/enum"
"github.com/samlior/libsam/pkg/swqos" "github.com/samlior/libsam/v2/pkg/swqos"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
) )

View File

@@ -11,7 +11,7 @@ import (
"github.com/gagliardetto/solana-go/rpc" "github.com/gagliardetto/solana-go/rpc"
"github.com/samlior/libsam/pkg/shreder" "github.com/samlior/libsam/v2/pkg/shreder"
) )
func main() { func main() {
@@ -76,9 +76,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,14 +90,10 @@ func main() {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case txBatch := <-txCh: case tx := <-txCh:
//jsonData, _ := json.MarshalIndent(txBatch, "", " ") if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" {
for _, tx := range txBatch { fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart))
if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" {
fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart))
}
} }
//fmt.Println(txBatch[0].TxHash)
} }
} }
} }

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

@@ -0,0 +1,191 @@
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/v2/pkg/shreder"
)
const (
rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
txSignature = "3hFamox2W1oWMwbRkfF5r9YiPULsdRsnR2TQsFDVtFCXf6cJ8ijGNgHGFmEbxEbVEryLg21sbt4qoGLwrPfvJ2UC"
labelFilter = ""
)
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 := parseSignals(context.Background(), update)
if len(signals) == 0 {
fmt.Println("no signals parsed")
return
}
printed := false
for _, signal := range signals {
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 {
output, err := json.MarshalIndent(signal, "", " ")
if err != nil {
log.Fatalf("marshal signal failed: %v", err)
}
fmt.Println(string(output))
}
}
func parseSignals(ctx context.Context, update *shreder.SubscribeUpdateTransaction) []shreder.TxSignal {
signalsCh := make(chan shreder.TxSignal, 64)
done := make(chan struct{})
go func() {
shreder.ParseTransactionForSubscribe(ctx, update, nil, signalsCh, done)
}()
go func() {
<-done
close(signalsCh)
}()
signals := make([]shreder.TxSignal, 0)
for signal := range signalsCh {
signals = append(signals, signal)
}
return signals
}
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,
}
}

46
go.mod
View File

@@ -1,50 +1,50 @@
module github.com/samlior/libsam module github.com/samlior/libsam/v2
go 1.25.1 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-20250908052524-06493dcc1bb4
github.com/gagliardetto/binary v0.8.0 github.com/gagliardetto/binary v0.8.0
github.com/gagliardetto/solana-go v1.12.0 github.com/gagliardetto/solana-go v1.14.0
github.com/hashicorp/golang-lru/v2 v2.0.7 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/quic-go/quic-go v0.59.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.78.0
google.golang.org/protobuf v1.36.10 google.golang.org/protobuf v1.36.11
) )
require ( require (
filippo.io/edwards25519 v1.0.0-rc.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
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.18.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/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.18.3 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.11 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect
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/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect github.com/streamingfast/logging v0.0.0-20260108192805-38f96de0a641 // indirect
go.mongodb.org/mongo-driver v1.12.2 // indirect go.mongodb.org/mongo-driver v1.17.7 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect
go.uber.org/zap v1.21.0 // indirect go.uber.org/zap v1.27.1 // indirect
golang.org/x/crypto v0.44.0 // indirect golang.org/x/crypto v0.47.0 // indirect
golang.org/x/net v0.47.0 // indirect golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.18.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.38.0 // indirect golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.37.0 // indirect golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.31.0 // indirect golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
) )

140
go.sum
View File

@@ -1,26 +1,25 @@
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250722092120-44561cb37455 h1:MMc4/hemlMtG76VkDZt6zNL0u+OwNYBCNp2KX5jqolQ= github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250908052524-06493dcc1bb4 h1:yvrhmN9vQIrquQP1fYul30khwfoE8oEL0VmwFZ37Mq8=
github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250722092120-44561cb37455/go.mod h1:vKj1SKlrekR9fuZgWQNNAWt/PUZIfzpGjDpIcbf1kT0= github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250908052524-06493dcc1bb4/go.mod h1:vKj1SKlrekR9fuZgWQNNAWt/PUZIfzpGjDpIcbf1kT0=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg= github.com/gagliardetto/binary v0.8.0 h1:U9ahc45v9HW0d15LoN++vIXSJyqR/pWw8DDlhd7zvxg=
github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c= github.com/gagliardetto/binary v0.8.0/go.mod h1:2tfj51g5o9dnvsc+fL3Jxr22MuWzYXwx9wEoN0XQ7/c=
github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw=
github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY=
github.com/gagliardetto/solana-go v1.12.0 h1:rzsbilDPj6p+/DOPXBMLhwMZeBgeRuXjm5zQFCoXgsg= github.com/gagliardetto/solana-go v1.14.0 h1:3WfAi70jOOjAJ0deFMjdhFYlLXATF4tOQXsDNWJtOLw=
github.com/gagliardetto/solana-go v1.12.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k= github.com/gagliardetto/solana-go v1.14.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k=
github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw=
github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@@ -29,8 +28,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -41,19 +38,18 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -61,7 +57,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk=
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
@@ -73,17 +68,16 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/panjf2000/ants/v2 v2.11.4 h1:UJQbtN1jIcI5CYNocTj0fuAUYvsLjPoYi0YuhqV/Y48= github.com/panjf2000/ants/v2 v2.11.4 h1:UJQbtN1jIcI5CYNocTj0fuAUYvsLjPoYi0YuhqV/Y48=
github.com/panjf2000/ants/v2 v2.11.4/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/panjf2000/ants/v2 v2.11.4/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyirb8anBEtdjtHFIufXdacyTi6i4KBfeNXeo=
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU=
github.com/streamingfast/logging v0.0.0-20260108192805-38f96de0a641 h1:dI+b2TyFS0rJw1xVGzxBBvn/RrSgBpqM/RjwDInxEfo=
github.com/streamingfast/logging v0.0.0-20260108192805-38f96de0a641/go.mod h1:fJ5nP7ZSMB4MQQ6RM7cF+LiSQ43b5cVletcSUNL8z2M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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=
@@ -92,110 +86,94 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.7 h1:a9w+U3Vt67eYzcfq3k/OAv284/uUUkL0uP75VE5rCOU=
go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws= go.mongodb.org/mongo-driver v1.17.7/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
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/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= 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/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -1,6 +1,6 @@
package consts package consts
import "github.com/samlior/libsam/pkg/enum" import "github.com/samlior/libsam/v2/pkg/enum"
var SWQoSFeeAddresses = map[string]string{ var SWQoSFeeAddresses = map[string]string{
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5": enum.SWQoSAgentJito, "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5": enum.SWQoSAgentJito,

View File

@@ -17,6 +17,8 @@ type TableInfo struct {
addresses []solana.PublicKey addresses []solana.PublicKey
} }
const MaxOverErrCount = 10
type AddressTables struct { type AddressTables struct {
showTableLoaded bool showTableLoaded bool
@@ -96,7 +98,7 @@ func (at *AddressTables) load(tablePubkey solana.PublicKey) {
}) })
} }
func (at *AddressTables) FillToTx(tx *versionedTransaction, 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 +114,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.StaticAccountKeys = append(tx.StaticAccountKeys, addresses.addresses[i])
} }
return true return true
} }
@@ -129,7 +131,7 @@ func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uin
if int(i) >= len(addresses.addresses) { if int(i) >= len(addresses.addresses) {
logger.Error("over loadAddressTable failed", "idx", i, "table", tablePubkey) logger.Error("over loadAddressTable failed", "idx", i, "table", tablePubkey)
addresses.overErrCount++ addresses.overErrCount++
if addresses.overErrCount > 10 { if addresses.overErrCount > MaxOverErrCount {
at.load(tablePubkey) at.load(tablePubkey)
} }
break break

View File

@@ -1,7 +1,9 @@
package shreder package shreder
import ( import (
"bytes"
"context" "context"
"errors"
"fmt" "fmt"
"runtime" "runtime"
"time" "time"
@@ -47,6 +49,8 @@ func BlocksStats(enable bool) ClientOption {
} }
} }
// LogParsedStats enables logging of parsed transaction statistics.
// Deprecated: do not use.
func LogParsedStats(enable bool) ClientOption { func LogParsedStats(enable bool) ClientOption {
return func(opts *ClientOpts) { return func(opts *ClientOpts) {
opts.logParseStats = enable opts.logParseStats = enable
@@ -113,7 +117,40 @@ 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
}
}
err = c.pool.Submit(func() {
ParseTransactionForEntries(ctx, slot, bytes.NewReader(response.Entries), c.tableLoader, txCh)
})
if err != nil && errors.Is(err, ants.ErrPoolOverload) {
logger.Warn("task pool is full")
}
}
}
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
@@ -152,24 +189,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) ParseTransactionForSubscribe(ctx, txData, c.tableLoader, txCh, nil)
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 && errors.Is(err, ants.ErrPoolOverload) {
break logger.Warn("task pool is full")
} }
} }

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

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

@@ -0,0 +1,272 @@
package shreder
import (
"encoding/binary"
"fmt"
"io"
"time"
)
type wrapperReader struct {
io.Reader
}
func (wr *wrapperReader) ReadU64() (uint64, error) {
var buf [8]byte
_, err := io.ReadFull(wr, buf[:])
if err != nil {
return 0, err
}
return uint64(buf[0]) | uint64(buf[1])<<8 | uint64(buf[2])<<16 | uint64(buf[3])<<24 |
uint64(buf[4])<<32 | uint64(buf[5])<<40 | uint64(buf[6])<<48 | uint64(buf[7])<<56, nil
}
func (wr *wrapperReader) Skip(n int) error {
_, err := io.CopyN(io.Discard, wr, int64(n))
return err
}
func (wr *wrapperReader) ReadCompactU16() (uint16, error) {
ln := 0
size := 0
var buf [1]byte
for i := 0; i < 3; i++ {
_, err := io.ReadFull(wr, buf[:])
if err != nil {
return 0, fmt.Errorf("unable to decode compact u16 at %d: %w", i, err)
}
elem := int(buf[0])
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
}
}
return uint16(ln), nil
}
func (wr *wrapperReader) ReadByte() (uint8, error) {
var buf [1]byte
_, err := io.ReadFull(wr, buf[:])
if err != nil {
return 0, err
}
return buf[0], nil
}
func ResizeSlice[T any](slice []T, newSize int) []T {
if cap(slice) < newSize {
slice = append(slice, make([]T, newSize-len(slice))...)
}
return slice[:newSize]
}
// entriesToVersionedTransaction converts raw entry bytes to versioned transactions.
func entriesToVersionedTransaction(slot uint64, data io.Reader, callback func(tx VersionedTransaction)) error {
b := &wrapperReader{data}
var entriesNumBuf [8]byte
n, err := io.ReadFull(b, entriesNumBuf[:])
if err != nil {
if err == io.EOF && n == 0 {
return nil
}
return fmt.Errorf("unable to read entries num: %w", err)
}
entriesNum := binary.LittleEndian.Uint64(entriesNumBuf[:])
//if entriesNum == 0 {
// return nil, nil
//}
if entriesNum > 2048 {
return fmt.Errorf("entries num is too large: %d > %d", entriesNum, 2048)
}
for i := uint64(0); i < entriesNum; i++ {
err = b.Skip(40)
if err != nil {
return fmt.Errorf("failed to skip num_hashes + hash of entry %d: %w", i, err)
}
numTx, err := b.ReadU64()
if err != nil {
return 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 fmt.Errorf("failed to read numSignatures in entry %d, txn %d: %w", i, j, err)
}
// enforce a maximum number of signatures to prevent OOM
if numSignatures > 32 {
return fmt.Errorf("numSignatures %d exceeds maximum in entry %d, txn %d", numSignatures, i, j)
}
if numSignatures == 0 {
return fmt.Errorf("numSignatures is zero in entry %d, txn %d", i, j)
}
versioned := VersionedTransaction{}
versioned.Block = slot
versioned.Time = time.Now()
versioned.Signatures = ResizeSlice(versioned.Signatures, int(numSignatures))
for k := 0; k < int(numSignatures); k++ {
_, err = io.ReadFull(b, versioned.Signatures[k][:])
if err != nil {
return fmt.Errorf("unable to read signature in entry %d, txn %d, sig: %d, %w", i, j, k, err)
}
}
msgVersion, err := b.ReadByte()
if err != nil {
return 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 := 2
if !legacy {
headerSkip = 3
}
// skip msg version, mx.Header+3
err = b.Skip(headerSkip)
if err != nil {
return 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 fmt.Errorf("unable to decode numAccountKeys in entry %d, txn %d: %w", i, j, err)
}
// enforce a maximum number of account keys to prevent OOM
if numAccountKeys > 255 {
return fmt.Errorf("numAccountKeys %d exceeds maximum in entry %d, txn %d", numAccountKeys, i, j)
}
versioned.StaticAccountKeys = ResizeSlice(versioned.StaticAccountKeys, int(numAccountKeys))
for k := 0; k < int(numAccountKeys); k++ {
_, err = io.ReadFull(b, versioned.StaticAccountKeys[k][:])
if err != nil {
return fmt.Errorf("unable to read accountKey[%d] in entry %d, txn %d: %w", k, i, j, err)
}
}
//skip solana hash
err = b.Skip(32)
if err != nil {
return 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 fmt.Errorf("unable to decode numInstructions in entry %d, txn %d: %w", i, j, err)
}
// enforce a maximum number of instructions to prevent OOM
if numInstructions >= 256 {
return fmt.Errorf("numInstructions %d exceeds maximum in entry %d, txn %d, txHash: %s", numInstructions, i, j, versioned.GetSignature())
}
versioned.Instructions = ResizeSlice(versioned.Instructions, int(numInstructions))
for k := 0; k < int(numInstructions); k++ {
versioned.Instructions[k].ProgramIDIndex, err = b.ReadByte()
if err != nil {
return 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 fmt.Errorf("unable to decode numAccounts for ix[%d] in entry %d, txn %d: %w", k, i, j, err)
}
// enforce a maximum number of accounts to prevent OOM
if numAccounts >= 256 {
return fmt.Errorf("numAccounts %d exceeds maximum for ix[%d] in entry %d, txn %d", numAccounts, k, i, j)
}
versioned.Instructions[k].Accounts = ResizeSlice(versioned.Instructions[k].Accounts, int(numAccounts))
//.AccountsLen = int(numAccounts)
if numAccounts != 0 {
_, err = io.ReadFull(b, versioned.Instructions[k].Accounts)
if err != nil {
return fmt.Errorf("unable to read mx.Instructions[%d].Accounts in entry %d, txn %d: %w", k, i, j, err)
}
}
dataLen, err := b.ReadCompactU16()
if err != nil {
return fmt.Errorf("unable to decode mx.Instructions[%d].Data length in entry %d, txn %d: %w", k, i, j, err)
}
// enforce a maximum data length to prevent OOM
if dataLen > 2048 {
return fmt.Errorf("mx.Instructions[%d].Data length %d exceeds maximum in entry %d, txn %d, txHash: %s", k, dataLen, i, j, versioned.GetSignature())
}
versioned.Instructions[k].Data = ResizeSlice(versioned.Instructions[k].Data, int(dataLen))
if dataLen > 0 {
_, err = io.ReadFull(b, versioned.Instructions[k].Data)
if err != nil {
return 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.ReadByte()
if err != nil {
return fmt.Errorf("unable to read numAddressTableLookups in entry %d, txn %d: %w", i, j, err)
}
if numLookups >= 32 {
return fmt.Errorf("numLookups %d exceeds maximum in entry %d, txn %d", numLookups, i, j)
}
versioned.AddressTableLookups = ResizeSlice(versioned.AddressTableLookups, int(numLookups))
for k := uint8(0); k < numLookups; k++ {
_, err = io.ReadFull(b, versioned.AddressTableLookups[k].AccountKey[:])
if err != nil {
return 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 fmt.Errorf("unable to decode numWritableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
}
// enforce a maximum number of writable indexes to prevent OOM
if numWritable >= 256 {
return fmt.Errorf("numWritableIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numWritable, k, i, j)
}
versioned.AddressTableLookups[k].WritableIndexes = ResizeSlice(versioned.AddressTableLookups[k].WritableIndexes, int(numWritable))
if numWritable > 0 {
_, err = io.ReadFull(b, versioned.AddressTableLookups[k].WritableIndexes)
if err != nil {
return fmt.Errorf("unable to read writableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
}
}
numReadonly, err := b.ReadCompactU16()
if err != nil {
return fmt.Errorf("unable to decode numReadonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
}
// enforce a maximum number of readonly indexes to prevent OOM
if numReadonly > 256 {
return fmt.Errorf("numReadonlyIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numReadonly, k, i, j)
}
versioned.AddressTableLookups[k].ReadonlyIndexes = ResizeSlice(versioned.AddressTableLookups[k].ReadonlyIndexes, int(numReadonly))
if numReadonly > 0 {
_, err = io.ReadFull(b, versioned.AddressTableLookups[k].ReadonlyIndexes)
if err != nil {
return fmt.Errorf("unable to read readonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
}
}
}
}
callback(versioned)
}
}
return nil
}

30
pkg/shreder/entry_test.go Normal file

File diff suppressed because one or more lines are too long

129
pkg/shreder/program_azcz.go Normal file
View File

@@ -0,0 +1,129 @@
package shreder
import (
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
"github.com/shopspring/decimal"
)
// has no sell function with pump and pump.amm program
var azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB")
var (
azczBuyTokensIX = []byte{11}
azczAmmBuyTokensIX = []byte{0xf}
)
type azczBuyArgs struct {
SolAmount uint64
TokenAmount uint64
}
func parseAzczInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
var (
err error
txSignal *TxSignal
)
if matchMethod(instruction.Data, azczBuyTokensIX) {
txSignal, err = parseAzczBuy(tx, instructionIndex)
} else if matchMethod(instruction.Data, azczAmmBuyTokensIX) {
txSignal, err = parseAzczAmmBuy(tx, instructionIndex)
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parseAzczAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
if len(instruction.Data) < 17 {
return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(instruction.Data))
}
solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "azcz",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: 0,
Token1AmountUint64: solAmount,
}, nil
}
func parseAzczBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
if len(instruction.Data) < 2 {
return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(instruction.Data))
}
var args azczBuyArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "azcz",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: args.SolAmount,
}, nil
}

View File

@@ -0,0 +1,111 @@
package shreder
import (
"encoding/binary"
"fmt"
"strings"
"github.com/gagliardetto/solana-go"
)
var bloomRouterProgramID = solana.MustPublicKeyFromBase58("b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1")
type bloomRouterArgs struct {
Side uint16
SolAmount uint64
TokenAmount uint64
}
func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) < 26 {
return nil, nil
}
var (
amount uint64
sol uint64
exactIn bool
event string
)
args, err := decodeBloomRouterArgs(instruction.Data)
if err != nil {
return nil, err
}
switch args.Side {
case 0:
event = "buy"
exactIn = true
case 1:
event = "sell"
default:
return nil, nil
}
if args.SolAmount > ^uint64(0)/100 {
return nil, fmt.Errorf("bloomrouter sol amount overflow")
}
// bloomrouter SOL amount has 2 fewer decimals than lamports.
sol = args.SolAmount * 100
amount = args.TokenAmount
if len(instruction.Accounts) == 0 {
return nil, fmt.Errorf("accounts too short")
}
maker, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
var (
mint solana.PublicKey
ok bool
)
for _, acctIdx := range instruction.Accounts {
key, err := tx.GetAccount(int(acctIdx))
if err != nil {
return nil, err
}
if strings.HasSuffix(key.String(), "pump") {
mint = key
ok = true
break
}
}
if !ok {
return nil, nil
}
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "bloomrouter",
Maker: maker.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(sol),
Program: "Pump",
Event: event,
ExactSOL: exactIn,
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: amount,
Token1AmountUint64: sol,
}}, nil
}
func decodeBloomRouterArgs(data []byte) (bloomRouterArgs, error) {
if len(data) < 26 {
return bloomRouterArgs{}, fmt.Errorf("data too short for bloomrouter args, len=%d", len(data))
}
return bloomRouterArgs{
Side: binary.BigEndian.Uint16(data[8:10]),
SolAmount: binary.LittleEndian.Uint64(data[10:18]),
TokenAmount: binary.LittleEndian.Uint64(data[18:26]),
}, nil
}

View File

@@ -0,0 +1,77 @@
package shreder
import (
"bytes"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
"github.com/shopspring/decimal"
)
var boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM")
var (
boboBuyPumpTokensIX = []byte{0xff, 0xe7, 0x11, 0x53, 0x15, 0xc5, 0xc3, 0xdf}
)
type boboBuyArgs struct {
Placeholder1 uint64
Placeholder2 uint64
SolAmount uint64
Placeholder3 uint64
Placeholder4 uint64
Placeholder5 uint64
Placeholder6 uint64
}
func parseBoboInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if len(instruction.Data) < 8 || !bytes.Equal(instruction.Data[:8], boboBuyPumpTokensIX) {
return nil, nil
}
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 16 {
return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(instruction.Data))
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
var args boboBuyArgs
if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "bobo",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.NewFromInt(1),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: 1,
Token1AmountUint64: args.SolAmount,
}}, nil
}

111
pkg/shreder/program_bonk.go Normal file
View File

@@ -0,0 +1,111 @@
package shreder
import (
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
)
var bonkProgramID = solana.MustPublicKeyFromBase58("BBRouter1cVunVXvkcqeKkZQcBK7ruan37PPm3xzWaXD")
var (
bonkBuyAndSellTokensIX = []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}
)
func parseBonkInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
var (
err error
txSignal *TxSignal
)
if matchMethod(instruction.Data, bonkBuyAndSellTokensIX) {
txSignal, err = parseBonkBuyAndSell(tx, instruction)
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parseBonkBuyAndSell(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
programId, err := tx.GetAccount(int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
if programId != pumpProgramID {
return nil, nil
}
user, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
flagAccount, err := tx.GetAccount(int(instruction.Accounts[4]))
if err != nil {
return nil, err
}
amount1 := binary.LittleEndian.Uint64(instruction.Data[17:25])
amount2 := binary.LittleEndian.Uint64(instruction.Data[25:33])
if user == flagAccount {
mint, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "bonk",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount2),
Token1Amount: formatSolAmount(amount1),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: amount2,
Token1AmountUint64: amount1,
}, nil
} else {
mint, err := tx.GetAccount(int(instruction.Accounts[5]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "bonk",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount1),
Token1Amount: formatSolAmount(amount2),
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block,
Token0AmountUint64: amount1,
Token1AmountUint64: amount2,
}, nil
}
}

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,12 +232,38 @@ func decodeSwap2Params(data []byte) (*dflowSwapParams, error) {
return out, nil return out, nil
} }
func parseDFlowInstruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { func findDflowPumpAmmMints(tx VersionedTransaction, accounts []uint8) (solana.PublicKey, solana.PublicKey, bool, error) {
msg := tx.Message for i, acctIdx := range accounts {
if instructionIndex >= len(msg.Instructions) { key, err := tx.GetAccount(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 := tx.GetAccount(int(accounts[baseIdx]))
if err != nil {
return solana.PublicKey{}, solana.PublicKey{}, false, err
}
quoteMint, err := tx.GetAccount(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) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds") return nil, fmt.Errorf("instruction index out of bounds")
} }
ix := msg.Instructions[instructionIndex] ix := tx.Instructions[instructionIndex]
if len(ix.Data) < 8 { if len(ix.Data) < 8 {
return nil, nil return nil, nil
} }
@@ -262,63 +288,121 @@ func parseDFlowInstruction(tx *versionedTransaction, instructionIndex int) (*TxS
return nil, nil return nil, nil
} }
var pump *pumpFunAmm var (
pumpAmmBuy *pumpFunAction
pumpAmmSell *pumpFunAction
pumpBuy *pumpFunAction
pumpSell *pumpFunAction
)
for _, act := range params.Actions { for _, act := range params.Actions {
if act.Tag == ActPumpFunAmmSell && act.Pump != nil { if act.Pump == nil {
pump = act.Pump continue
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 {
return nil, nil // only care about PumpFunAmmSell
}
// Require WSOL pair when destination mint is provided. out := make(TxSignalBatch, 0, 2)
var ( if pumpAmmSell != nil || pumpAmmBuy != nil {
srcIdx uint8 event := "sell"
) amt := pumpAmmSell
if len(ix.Accounts) <= 6 { isBuy := false
return nil, nil if amt == nil {
} event = "buy"
accounts := ix.Accounts[5:] isBuy = true
for i, acctIdx := range accounts { amt = pumpAmmBuy
key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) }
baseMint, quoteMint, ok, err := findDflowPumpAmmMints(tx, ix.Accounts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if key.Equals(pumpAmmProgramID) { if ok && quoteMint.Equals(solana.WrappedSol) {
srcIdx = uint8(i + 4) var (
break 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.StaticAccountKeys[0].String(),
Program: "PumpAMM",
Event: event,
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: token0Amount,
Token1Amount: token1Amount,
ExactSOL: exactSol,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64,
})
} }
} }
if srcIdx == 0 || srcIdx+1 >= uint8(len(accounts)) {
return nil, 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, 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.StaticAccountKeys[0].String(),
Program: "Pump",
Event: event,
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: token0Amount,
Token1Amount: token1Amount,
ExactSOL: exactSol,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64,
})
}
} }
baseMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx])) if len(out) == 0 {
if err != nil {
return nil, err
}
quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(accounts[srcIdx+1]))
if err != nil {
return nil, err
}
if !quoteMint.Equals(solana.WrappedSol) {
return nil, nil return nil, nil
} }
return out, nil
// Build TxSignal
sig := &TxSignal{
TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(),
Program: "PumpAMM",
Event: "sell",
Token0Address: baseMint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(pump.Amount),
Token1Amount: decimal.Zero,
Token0AmountUint64: uint64(pump.Amount),
Token1AmountUint64: 0,
}
return sig, nil
} }

227
pkg/shreder/program_dlmm.go Normal file
View File

@@ -0,0 +1,227 @@
package shreder
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
// For Metaora dlmm
var dlmmProgramID = solana.MustPublicKeyFromBase58("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo")
var (
dlmmSwapIX = []byte{248, 198, 158, 145, 225, 117, 135, 200}
dlmmSwap2IX = []byte{65, 75, 63, 76, 235, 91, 91, 136}
dlmmSwapExactOutIX = []byte{250, 73, 101, 33, 38, 207, 75, 184}
dlmmSwapExactOut2IX = []byte{43, 215, 247, 132, 137, 60, 243, 81}
dlmmSwapPriceImpactIX = []byte{56, 173, 230, 208, 173, 228, 156, 205}
dlmmSwapPriceImpact2IX = []byte{74, 98, 192, 214, 177, 51, 75, 51}
)
type dlmmParsedArgs struct {
AmountIn uint64
AmountOut uint64
ExactIn bool
ExactOut bool
ActiveBin int32
MaxPriceImpactBps uint16
}
func dlmmTokenOrder(tokenX, tokenY solana.PublicKey) (solana.PublicKey, solana.PublicKey) {
switch {
case tokenX.Equals(solana.WrappedSol):
return tokenY, tokenX
case tokenY.Equals(solana.WrappedSol):
return tokenX, tokenY
default:
return tokenX, tokenY
}
}
func parseDlmmSwapArgs(disc []byte, payload []byte) (*dlmmParsedArgs, error) {
switch {
case bytes.Equal(disc, dlmmSwapIX), bytes.Equal(disc, dlmmSwap2IX):
if len(payload) < 16 {
return nil, fmt.Errorf("data too short for dlmm swap args, len=%d", len(payload))
}
return &dlmmParsedArgs{
AmountIn: binary.LittleEndian.Uint64(payload[0:8]),
AmountOut: binary.LittleEndian.Uint64(payload[8:16]),
ExactIn: true,
}, nil
case bytes.Equal(disc, dlmmSwapExactOutIX), bytes.Equal(disc, dlmmSwapExactOut2IX):
if len(payload) < 16 {
return nil, fmt.Errorf("data too short for dlmm swap exact out args, len=%d", len(payload))
}
return &dlmmParsedArgs{
AmountIn: binary.LittleEndian.Uint64(payload[0:8]),
AmountOut: binary.LittleEndian.Uint64(payload[8:16]),
ExactOut: true,
}, nil
case bytes.Equal(disc, dlmmSwapPriceImpactIX), bytes.Equal(disc, dlmmSwapPriceImpact2IX):
if len(payload) < 11 {
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
}
amountIn := binary.LittleEndian.Uint64(payload[0:8])
idx := 8
if len(payload) < idx+1 {
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
}
activeBinTag := payload[idx]
idx++
var activeBin int32
if activeBinTag == 1 {
if len(payload) < idx+4 {
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
}
activeBin = int32(binary.LittleEndian.Uint32(payload[idx : idx+4]))
idx += 4
} else if activeBinTag != 0 {
return nil, fmt.Errorf("invalid active_id tag %d", activeBinTag)
}
if len(payload) < idx+2 {
return nil, fmt.Errorf("data too short for dlmm swap with price impact args, len=%d", len(payload))
}
return &dlmmParsedArgs{
AmountIn: amountIn,
ExactIn: true,
ActiveBin: activeBin,
MaxPriceImpactBps: binary.LittleEndian.Uint16(payload[idx : idx+2]),
}, nil
default:
return nil, nil
}
}
func parseDlmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) < 8 {
return nil, fmt.Errorf("data is empty")
}
if len(instruction.Accounts) < 13 {
return nil, nil // fmt.Errorf("accounts too short")
}
disc := instruction.Data[:8]
payload := instruction.Data[8:]
args, err := parseDlmmSwapArgs(disc, payload)
if err != nil {
return nil, err
}
if args == nil {
return nil, nil
}
userTokenIn, err := tx.GetAccount(int(instruction.Accounts[4]))
if err != nil {
return nil, err
}
userTokenOut, err := tx.GetAccount(int(instruction.Accounts[5]))
if err != nil {
return nil, err
}
tokenX, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
tokenY, err := tx.GetAccount(int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[10]))
if err != nil {
return nil, err
}
tokenXProgram, err := tx.GetAccount(int(instruction.Accounts[11]))
if err != nil {
return nil, err
}
tokenYProgram, err := tx.GetAccount(int(instruction.Accounts[12]))
if err != nil {
return nil, err
}
token0Mint, token1Mint := dlmmTokenOrder(tokenX, tokenY)
var (
token0AmountUint64 uint64
token1AmountUint64 uint64
)
if !tokenX.Equals(solana.WrappedSol) && !tokenY.Equals(solana.WrappedSol) {
return nil, nil
}
wsolProgram := tokenXProgram
if tokenY.Equals(solana.WrappedSol) {
wsolProgram = tokenYProgram
}
wsolAta, _, err := findAssociatedTokenAddressWithTokenProgram(user, solana.WrappedSol, wsolProgram)
if err != nil {
return nil, nil
}
wsolIn := userTokenIn.Equals(wsolAta)
wsolOut := userTokenOut.Equals(wsolAta)
if !wsolIn && !wsolOut {
return nil, nil
}
event := "sell"
if wsolIn {
event = "buy"
}
exactSol := (args.ExactIn && wsolIn) || (args.ExactOut && wsolOut)
if wsolIn {
if args.ExactIn {
token1AmountUint64 = args.AmountIn
}
if args.ExactOut {
token0AmountUint64 = args.AmountOut
}
} else {
if args.ExactOut {
token1AmountUint64 = args.AmountOut
}
if args.ExactIn {
token0AmountUint64 = args.AmountIn
}
}
token0Amount := formatTokenAmount(token0AmountUint64)
if token0Mint.Equals(solana.WrappedSol) {
token0Amount = formatSolAmount(token0AmountUint64)
}
token1Amount := decimal.Zero
if token1AmountUint64 > 0 {
if token1Mint.Equals(solana.WrappedSol) {
token1Amount = formatSolAmount(token1AmountUint64)
} else {
token1Amount = formatTokenAmount(token1AmountUint64)
}
}
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "dlmm",
Maker: user.String(),
Token0Address: token0Mint.String(),
Token1Address: token1Mint.String(),
Token0Amount: token0Amount,
Token1Amount: token1Amount,
Program: "MeteoraDLMM",
Event: event,
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: exactSol,
ActiveBin: args.ActiveBin,
MaxPriceImpactBps: args.MaxPriceImpactBps,
Block: tx.Block,
Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64,
}}, nil
}

View File

@@ -0,0 +1,72 @@
package shreder
import (
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
)
// only buy function with pump program
var f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq")
var (
f5tfBuyTokensIX = []byte{0}
)
type f5tfBuyArgs struct {
SolAmount uint64
TokenAmount uint64
}
func parseF5tfInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if !matchMethod(instruction.Data, f5tfBuyTokensIX) {
return nil, nil
}
if len(instruction.Accounts) < 7 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
if len(instruction.Data) < 2 {
return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(instruction.Data))
}
var args f5tfBuyArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "f5tf",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: args.SolAmount,
}}, nil
}

View File

@@ -0,0 +1,71 @@
package shreder
import (
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
)
// only buy function with pump program
var fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK")
var (
fjszBuyTokensIX = []byte{0xe7, 0x3f, 0x99, 0x83, 0xf3, 0xed, 0xe3, 0x3c}
)
type fjszBuyArgs struct {
SolAmount uint64
TokenAmount uint64
}
func parseFjszInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if !matchMethod(instruction.Data, fjszBuyTokensIX) {
return nil, nil
}
if len(instruction.Accounts) < 7 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 16 {
return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(instruction.Data))
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
var args fjszBuyArgs
if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "fjsz",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: args.SolAmount,
}}, nil
}

218
pkg/shreder/program_flas.go Normal file
View File

@@ -0,0 +1,218 @@
package shreder
import (
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
"github.com/shopspring/decimal"
)
var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
var (
flasBuyTokensIX = []byte{0x00, 0x1, 0x4}
flasSellTokensIX = []byte{0x01, 0x1, 0x3}
flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2}
flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2}
)
type flasArgs struct {
Amount1 uint64
Amount2 uint64
Placeholder [3]uint8
}
func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if len(instruction.Data) == 10 && instruction.Data[0] == 1 {
return nil, nil
}
if len(instruction.Data) < 20 {
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data))
}
methodData := instruction.Data[17:20]
//if !matchMethod(methodData, flasBuyTokensIX) {
// return nil, nil
//}
var (
err error
txSignal *TxSignal
)
if matchMethod(methodData, flasBuyTokensIX) {
txSignal, err = parseFlasBuy(tx, instructionIndex)
} else if matchMethod(methodData, flasSellTokensIX) {
txSignal, err = parseFlasSell(tx, instructionIndex)
} else if matchMethod(methodData, flasAmmBuyTokensIX) {
txSignal, err = parseFlasAmmBuy(tx, instructionIndex)
} else if matchMethod(methodData, flasAmmSellTokensIX) {
txSignal, err = parseFlasAmmSell(tx, instructionIndex)
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 10 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[9]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "flas",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.Amount1),
Token1Amount: formatSolAmount(args.Amount2),
Program: "PumpAMM",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block,
Token0AmountUint64: args.Amount1,
Token1AmountUint64: args.Amount2,
}, nil
}
func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 10 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[9]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "flas",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: formatSolAmount(args.Amount1),
Program: "PumpAMM",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: 0,
Token1AmountUint64: args.Amount1,
}, nil
}
func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 9 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[8]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "flas",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.Amount1),
Token1Amount: formatSolAmount(args.Amount2),
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: args.Amount1,
Token1AmountUint64: args.Amount2,
}, nil
}
func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 9 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[8]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
if len(instruction.Data) > 20 {
instruction.Data = instruction.Data[:20]
}
var args flasArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "flas",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.Amount2),
Token1Amount: formatSolAmount(args.Amount1),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: args.Amount2,
Token1AmountUint64: args.Amount1,
}, nil
}

View File

@@ -0,0 +1,78 @@
package shreder
import (
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
)
var gmgnProgramID = solana.MustPublicKeyFromBase58("GMgnVFR8Jb39LoXsEVzb3DvBy3ywCmdmJquHUy1Lrkqb")
var (
gmgnBuyTokensIX = []byte{0x66, 0x06, 0x3d, 0x12, 0x01, 0xda, 0xeb, 0xea}
)
func parseGMGNInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if len(instruction.Data) < 8 {
return nil, nil
}
var (
err error
txSignal *TxSignal
)
if matchMethod(instruction.Data, gmgnBuyTokensIX) {
txSignal, err = parseGMGNBuy(tx, instruction)
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parseGMGNBuy(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 24 {
return nil, fmt.Errorf("data too short for gmgn buy args, len=%d", len(instruction.Data))
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16])
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "gmgn",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -245,12 +245,11 @@ type OkxV2SwapScorch struct {
Id [16]byte Id [16]byte
} }
func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
msg := tx.Message if instructionIndex >= len(tx.Instructions) {
if instructionIndex >= len(msg.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds") return nil, fmt.Errorf("instruction index out of bounds")
} }
ix := msg.Instructions[instructionIndex] ix := tx.Instructions[instructionIndex]
if len(ix.Data) < 8 { if len(ix.Data) < 8 {
return nil, nil return nil, nil
} }
@@ -310,7 +309,7 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
return nil, nil return nil, nil
} }
srcMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(ix.Accounts[3])) srcMint, err := tx.GetAccount(int(ix.Accounts[3]))
var ( var (
srcIdx uint8 srcIdx uint8
@@ -320,7 +319,7 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
} }
accounts := ix.Accounts[14:] accounts := ix.Accounts[14:]
for i, acctIdx := range accounts { for i, acctIdx := range accounts {
key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) key, err := tx.GetAccount(int(acctIdx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -333,7 +332,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(int(accounts[srcIdx]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -341,7 +340,7 @@ 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(int(accounts[srcIdx+1]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -349,9 +348,9 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
return nil, nil return nil, nil
} }
return &TxSignal{ return TxSignalBatch{&TxSignal{
TxHash: tx.Signatures[0].String(), TxHash: tx.Signatures[0].String(),
Maker: tx.Message.StaticAccountKeys[0].String(), Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(), Token0Address: baseMint.String(),
Token1Address: wsolMint, Token1Address: wsolMint,
Token0Amount: formatTokenAmount(inputAmount), Token0Amount: formatTokenAmount(inputAmount),
@@ -364,5 +363,5 @@ func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex in
ExactSOL: false, ExactSOL: false,
Token0AmountUint64: inputAmount, Token0AmountUint64: inputAmount,
Token1AmountUint64: 0, Token1AmountUint64: 0,
}, nil }}, nil
} }

View File

@@ -0,0 +1,156 @@
package shreder
import (
"bytes"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
)
// only pump.fun function
var photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW")
var (
photonBuyPumpTokensIX = []byte{0x52, 0xe1, 0x77, 0xe7, 0x4e, 0x1d, 0x2d, 0x46}
photonSwapPumpAmmIX = []byte{0x2c, 0x77, 0xaf, 0xda, 0xc7, 0x4d, 0xc4, 0xeb}
)
type photonBuyPumpArgs struct {
Timestamp uint64
SolAmount uint64
TokenAmount uint64
Fee uint64
}
type photonSwapPumpAmmArgs struct {
FromAmount uint64
ToAmount uint64
}
func parsePhotonInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if len(instruction.Data) < 8 {
return nil, nil
}
var (
err error
txSignal *TxSignal
)
switch {
case bytes.Equal(instruction.Data[:8], photonBuyPumpTokensIX):
txSignal, err = parsePhotonBuy(tx, instruction)
case bytes.Equal(instruction.Data[:8], photonSwapPumpAmmIX):
txSignal, err = parsePhotonSwap(tx, instruction)
default:
return nil, nil
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parsePhotonBuy(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 16 {
return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data))
}
mint, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
var args photonBuyPumpArgs
if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
solAmount := args.SolAmount * (100000000 - 1234568) / 100000000
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "photon",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: args.TokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
func parsePhotonSwap(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 16 {
return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data))
}
base, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
quote, err := tx.GetAccount(int(instruction.Accounts[4]))
if err != nil {
return nil, err
}
if !quote.Equals(solana.WrappedSol) {
return nil, nil
}
buyer, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
var args photonSwapPumpAmmArgs
if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil {
return nil, fmt.Errorf("failed to parse swap pump amm tokens args: %w", err)
}
if args.FromAmount > args.ToAmount {
// sell; ignore
return nil, nil
}
solAmount := args.FromAmount * (100000000 - 1234568) / 100000000
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "photon",
Maker: buyer.String(),
Token0Address: base.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.ToAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "PumpAMM",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: args.ToAmount,
Token1AmountUint64: solAmount,
}, nil
}

256
pkg/shreder/program_pump.go Normal file
View File

@@ -0,0 +1,256 @@
package shreder
import (
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
"github.com/shopspring/decimal"
)
var pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
var (
pumpCreateCoinIX = []byte{24, 30, 200, 40, 5, 28, 7, 119}
pumpCreateCoinV2IX = []byte{214, 144, 76, 236, 95, 139, 49, 180}
pumpExtendedSellIX = []byte{51, 230, 133, 164, 1, 127, 131, 173}
pumpBuyTokensIX = []byte{102, 6, 61, 18, 1, 218, 235, 234}
pumpBuyV2TokensIX = []byte{56, 252, 116, 8, 158, 223, 205, 95}
)
type pumpExtendedSellArgs struct {
Amount uint64
MinSolOutput uint64
}
type pumpBuyArgs struct {
Amount uint64
MaxSolCost uint64
}
type pumpCreateCoinV2Args struct {
Name string
Symbol string
Uri string
Creator solana.PublicKey
IsMayhemMode bool
}
func parsePumpInstruction(msg VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
instruction := msg.Instructions[instructionIndex]
if len(instruction.Data) < 8 {
return nil, fmt.Errorf("data is empty")
}
var (
err error
txSignal *TxSignal
)
if matchMethod(instruction.Data[0:8], pumpBuyV2TokensIX) || matchMethod(instruction.Data[0:8], pumpBuyTokensIX) {
txSignal, err = parsePumpBuy(msg, instruction)
} else if matchMethod(instruction.Data[0:8], pumpExtendedSellIX) {
txSignal, err = parsePumpSell(msg, instruction)
} else if matchMethod(instruction.Data[0:8], pumpCreateCoinIX) {
txSignal, err = parsePumpCreate(msg, instruction)
} else if matchMethod(instruction.Data[0:8], pumpCreateCoinV2IX) {
txSignal, err = parsePumpCreateV2(msg, instruction)
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parsePumpCreate(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
creator, err := tx.GetAccount(int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "pump",
Maker: creator.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: decimal.Zero,
Program: "Pump",
Event: "create",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: 0,
Token1AmountUint64: 0,
}, nil
}
func parsePumpCreateV2(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 8 {
return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(instruction.Data))
}
mint, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
tokenProgramKey, err := tx.GetAccount(int(instruction.Accounts[7]))
if err != nil {
return nil, err
}
var args pumpCreateCoinV2Args
if err := borsh.Deserialize(&args, instruction.Data[8:]); err != nil {
return nil, fmt.Errorf("failed to parse create coin v2 args: %w", err)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "pump",
Maker: args.Creator.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: decimal.Zero,
Token1Amount: decimal.Zero,
Program: "Pump",
Event: "create",
IsToken2022: tokenProgramKey.String() != tokenProgram,
IsMayhemMode: args.IsMayhemMode,
Block: tx.Block,
Token0AmountUint64: 0,
Token1AmountUint64: 0,
}, nil
}
func decodePumpBuyArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 {
return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data))
}
var args pumpBuyArgs
if err := borsh.Deserialize(&args, data[8:]); err == nil {
return args.Amount, args.MaxSolCost, nil
}
if len(data) >= 24 {
amount := binary.LittleEndian.Uint64(data[8:16])
maxSol := binary.LittleEndian.Uint64(data[16:24])
return amount, maxSol, nil
}
return 0, 0, fmt.Errorf("failed to parse buy tokens args")
}
func parsePumpBuy(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
amount, sol, err := decodePumpBuyArgs(instruction.Data)
if err != nil {
return nil, err
}
exactIn := false
if matchMethod(instruction.Data, pumpBuyV2TokensIX) {
temp := amount
amount = sol
sol = temp
exactIn = true
}
if len(instruction.Accounts) < 7 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
buyer, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "pump",
Maker: buyer.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(sol),
Program: "Pump",
Event: "buy",
ExactSOL: exactIn,
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: amount,
Token1AmountUint64: sol,
}, nil
}
func decodePumpSellArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 {
return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data))
}
var args pumpExtendedSellArgs
if err := borsh.Deserialize(&args, data[8:]); err == nil {
return args.Amount, args.MinSolOutput, nil
}
if len(data) >= 24 {
amount := binary.LittleEndian.Uint64(data[8:16])
minSol := binary.LittleEndian.Uint64(data[16:24])
return amount, minSol, nil
}
return 0, 0, fmt.Errorf("failed to parse sell tokens args")
}
func parsePumpSell(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
amount, minSol, err := decodePumpSellArgs(instruction.Data)
if err != nil {
return nil, err
}
if len(instruction.Accounts) < 7 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
seller, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "pump",
Maker: seller.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(minSol),
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: amount,
Token1AmountUint64: minSol,
}, nil
}

View File

@@ -0,0 +1,166 @@
package shreder
import (
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
)
var pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
var (
pumpAmmBuyTokensV2IX = []byte{198, 46, 21, 82, 180, 217, 232, 112}
pumpAmmBuyTokensIX = []byte{102, 6, 61, 18, 1, 218, 235, 234}
pumpAmmSellTokensIX = []byte{51, 230, 133, 164, 1, 127, 131, 173}
)
type pumpAmmBuyArgs struct {
Amount uint64
MaxSolCost uint64
}
func parsePumpAmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
var (
err error
txSignal *TxSignal
)
if matchMethod(instruction.Data, pumpAmmBuyTokensIX) || matchMethod(instruction.Data, pumpAmmBuyTokensV2IX) {
txSignal, err = parsePumpAmmBuy(tx, instruction)
} else if matchMethod(instruction.Data, pumpAmmSellTokensIX) {
txSignal, err = parsePumpAmmSell(tx, instruction)
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 {
return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data))
}
var args pumpAmmBuyArgs
if err := borsh.Deserialize(&args, data[8:]); err == nil {
return args.Amount, args.MaxSolCost, nil
}
if len(data) >= 24 {
amount := binary.LittleEndian.Uint64(data[8:16])
maxSol := binary.LittleEndian.Uint64(data[16:24])
return amount, maxSol, nil
}
return 0, 0, fmt.Errorf("failed to parse buy tokens args")
}
func parsePumpAmmBuy(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
amount, maxSol, err := decodePumpAmmBuyArgs(instruction.Data)
if err != nil {
return nil, err
}
exactIn := false
if matchMethod(instruction.Data, pumpAmmBuyTokensV2IX) {
temp := amount
amount = maxSol
maxSol = temp
exactIn = true
}
if len(instruction.Accounts) < 7 {
return nil, fmt.Errorf("accounts too short")
}
base, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
quote, err := tx.GetAccount(int(instruction.Accounts[4]))
if err != nil {
return nil, err
}
if !quote.Equals(solana.WrappedSol) {
return nil, nil
}
buyer, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "pumpamm",
Maker: buyer.String(),
Token0Address: base.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(maxSol),
Program: "PumpAMM",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: exactIn,
Block: tx.Block,
Token0AmountUint64: amount,
Token1AmountUint64: maxSol,
}, nil
}
func parsePumpAmmSell(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
amount, minSol, err := decodePumpAmmBuyArgs(instruction.Data)
if err != nil {
return nil, err
}
if len(instruction.Accounts) < 7 {
return nil, fmt.Errorf("accounts too short")
}
base, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
quote, err := tx.GetAccount(int(instruction.Accounts[4]))
if err != nil {
return nil, err
}
if !quote.Equals(solana.WrappedSol) {
return nil, nil
}
buyer, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "pumpamm",
Maker: buyer.String(),
Token0Address: base.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(amount),
Token1Amount: formatSolAmount(minSol),
Program: "PumpAMM",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: amount,
Token1AmountUint64: minSol,
}, nil
}

166
pkg/shreder/program_qtkv.go Normal file
View File

@@ -0,0 +1,166 @@
package shreder
import (
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/near/borsh-go"
"github.com/shopspring/decimal"
)
var qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7")
var (
qtkvBuyTokensIX = []byte{0x02}
qtkvSellTokensIX = []byte{0x03}
qtkvAmmSellTokensIX = []byte{0x05}
)
type qtkvBuyArgs struct {
Placeholder uint64
TokenNumber uint64
SolAmount uint64
}
func parseQtkvInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
var (
err error
txSignal *TxSignal
)
if matchMethod(instruction.Data, qtkvBuyTokensIX) {
txSignal, err = parseQtkvBuy(tx, instructionIndex)
} else if matchMethod(instruction.Data, qtkvAmmSellTokensIX) {
txSignal, err = parseQtkvAmmSell(tx, instructionIndex)
} else if matchMethod(instruction.Data, qtkvSellTokensIX) {
txSignal, err = parseQtkvSell(tx, instructionIndex)
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parseQtkvSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 11 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 24 {
return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(instruction.Data))
}
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
// in sell, sol amount is not directly provided, so we set it to 0
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "qtkv",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: decimal.Zero,
Program: "Pump",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: 0,
}, nil
}
func parseQtkvAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 11 {
return nil, fmt.Errorf("accounts too short")
}
if len(instruction.Data) < 24 {
return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(instruction.Data))
}
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
// in sell, sol amount is not directly provided, so we set it to 0
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "qtkv",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: decimal.Zero,
Program: "PumpAMM",
Event: "sell",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: 0,
}, nil
}
func parseQtkvBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[0]))
if err != nil {
return nil, err
}
var args qtkvBuyArgs
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
}
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "qtkv",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(args.TokenNumber),
Token1Amount: formatSolAmount(args.SolAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
Block: tx.Block,
Token0AmountUint64: args.TokenNumber,
Token1AmountUint64: args.SolAmount,
}, nil
}

155
pkg/shreder/program_term.go Normal file
View File

@@ -0,0 +1,155 @@
package shreder
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/gagliardetto/solana-go"
)
var terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
var (
terminalBuyTokensIX = []byte{0xa6, 0x54, 0x14, 0x96, 0x9f, 0x77, 0x59, 0xca}
terminalSellTokensIX = []byte{0xbe, 0x84, 0xa2, 0x96, 0x93, 0x7c, 0xf8, 0x6b}
terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1}
)
func parseTermInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
if instructionIndex >= len(tx.Instructions) {
return nil, fmt.Errorf("instruction index out of bounds")
}
instruction := tx.Instructions[instructionIndex]
if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty")
}
if len(instruction.Data) < 24 {
return nil, nil
}
var (
err error
txSignal *TxSignal
)
switch {
case bytes.Equal(instruction.Data[:8], terminalBuyTokensIX):
txSignal, err = parseTermBuy(tx, instruction)
case bytes.Equal(instruction.Data[:8], terminalSellTokensIX):
txSignal, err = parseTermSell(tx, instruction)
case bytes.Equal(instruction.Data[:8], terminalAmmSellTokensIX):
txSignal, err = parseTermAmmSell(tx, instruction)
default:
return nil, nil
}
if txSignal != nil {
return TxSignalBatch{txSignal}, err
}
return nil, err
}
func parseTermAmmSell(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[3]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil {
return nil, err
}
solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16])
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "term",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
func parseTermBuy(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
solAmount := binary.LittleEndian.Uint64(instruction.Data[8:16])
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[16:24])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "term",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: true,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}
func parseTermSell(tx VersionedTransaction, instruction Instructions) (*TxSignal, error) {
if len(instruction.Accounts) < 8 {
return nil, fmt.Errorf("accounts too short")
}
mint, err := tx.GetAccount(int(instruction.Accounts[2]))
if err != nil {
return nil, err
}
user, err := tx.GetAccount(int(instruction.Accounts[6]))
if err != nil {
return nil, err
}
tokenAmount := binary.LittleEndian.Uint64(instruction.Data[8:16])
solAmount := binary.LittleEndian.Uint64(instruction.Data[16:24])
return &TxSignal{
TxHash: tx.Signatures[0].String(),
Label: "term",
Maker: user.String(),
Token0Address: mint.String(),
Token1Address: wsolMint,
Token0Amount: formatTokenAmount(tokenAmount),
Token1Amount: formatSolAmount(solAmount),
Program: "Pump",
Event: "buy",
IsToken2022: false,
IsMayhemMode: false,
ExactSOL: false,
Block: tx.Block,
Token0AmountUint64: tokenAmount,
Token1AmountUint64: solAmount,
}, nil
}

View File

@@ -48,6 +48,12 @@ 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:"-"`
@@ -56,10 +62,4 @@ type TxSignal struct {
ParseEnd time.Time `json:"parse_end"` ParseEnd time.Time `json:"parse_end"`
} }
func (t *TxSignal) Parse() *TxSignal {
t.Token0AmountUint64 = t.Token0Amount.Mul(decimal.New(1, TokenDecimals)).BigInt().Uint64()
t.Token1AmountUint64 = t.Token1Amount.Mul(decimal.New(1, SolDecimals)).BigInt().Uint64()
return t
}
type TxSignalBatch = []*TxSignal type TxSignalBatch = []*TxSignal

File diff suppressed because it is too large Load Diff

View File

@@ -149,11 +149,21 @@ func TestParseTermBuy(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction( ch := make(chan TxSignal)
getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"), closed := make(chan struct{})
nil, go func() {
false, ParseTransactionForSubscribe(
) context.Background(),
getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"),
nil,
ch,
closed,
)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
if len(signals) != 1 { if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals)) t.Fatalf("expected 1 signal, got %d", len(signals))
} }
@@ -186,11 +196,23 @@ func TestParseBonkBuy(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"), ch := make(chan TxSignal)
nil, closed := make(chan struct{})
false, go func() {
) ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "3gHF3TA2aA8rpjdmoEs2vA89vrq9J9NnTTUSXHfE6uXcaYP9cJgLtEUjCmsK9EWAyHEg7cEiepehQf4GFv1272jW"),
nil,
ch,
closed,
)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
if len(signals) != 1 { if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals)) t.Fatalf("expected 1 signal, got %d", len(signals))
} }
@@ -223,11 +245,22 @@ func TestParseBonkSell(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction( ch := make(chan TxSignal)
getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"), closed := make(chan struct{})
nil, go func() {
false, ParseTransactionForSubscribe(
) context.Background(),
getTransaction(t, client, "3XNi6b3j69SSStqLLRQVH5BNGVfEoFxGCzmpdd5FvrY4kmC8T644WGdEhCH9fAdrxWuR2Mtzgywq8K7qetu5MGyb"),
nil,
ch,
closed,
)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
if len(signals) != 1 { if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals)) t.Fatalf("expected 1 signal, got %d", len(signals))
} }
@@ -260,11 +293,23 @@ func TestParsePhotonBuy(t *testing.T) {
} }
client := rpc.New(rpcUrl) client := rpc.New(rpcUrl)
signals := ParseTransaction(
getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"), ch := make(chan TxSignal)
nil, closed := make(chan struct{})
false, go func() {
) ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "4DCEcXAWBxagXoUNGhWsJ7qfxq5SuE5BG2cBDBqAY7sCHkBopaMJu33ZnXnFHqzPMmWxVxq6666KRF4hMHVB33Ux"),
nil,
ch,
closed,
)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
if len(signals) != 1 { if len(signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(signals)) t.Fatalf("expected 1 signal, got %d", len(signals))
} }
@@ -289,3 +334,104 @@ 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)
ch := make(chan TxSignal)
closed := make(chan struct{})
go func() {
ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "4QF5whXwjx234fMXeH3HrJCy5knFJmKPtgbXys8xKGz1pZypqPvXBr4BoAqXfYn8jLL4HXPY1pcvxCCW1XREFNxd"),
nil,
ch,
closed,
)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
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)
ch := make(chan TxSignal)
closed := make(chan struct{})
go func() {
ParseTransactionForSubscribe(
context.Background(),
getTransaction(t, client, "yCnE7ZA8dqB5iAZtwpSN2ar5HXh3gBjgaG2xtnwXDPFyHAm5XFU8642uTZTH5A2iPQ6G9hrj5eEPAJiWrfe38gM"),
nil,
ch,
closed,
)
}()
signals := make([]TxSignal, 0)
for signal := range ch {
signals = append(signals, signal)
}
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")
}
}

62
pkg/shreder/versioned.go Normal file
View File

@@ -0,0 +1,62 @@
package shreder
import (
"fmt"
"time"
"github.com/gagliardetto/solana-go"
)
type AccountNotFoundError struct {
Index int
Len int
}
func NewAccountNotFoundError(i, l int) error {
return &AccountNotFoundError{i, l}
}
func (e AccountNotFoundError) Error() string {
return fmt.Sprintf("account index %d out of range, len=%d", e.Index, e.Len)
}
type Instructions struct {
ProgramIDIndex uint8
Accounts []uint8
Data []byte
}
type AddressTableLookup struct {
AccountKey solana.PublicKey
WritableIndexes []uint8
ReadonlyIndexes []uint8
}
type VersionedTransaction struct {
Signatures []solana.Signature
StaticAccountKeys []solana.PublicKey
Instructions []Instructions
AddressTableLookups []AddressTableLookup
Block uint64
Time time.Time
}
func (vt VersionedTransaction) GetSignature() string {
if len(vt.Signatures) == 0 {
return ""
}
return vt.Signatures[0].String()
}
func (vtp *VersionedTransaction) FillAccount(account solana.PublicKey) {
vtp.StaticAccountKeys = append(vtp.StaticAccountKeys, account)
}
func (vt VersionedTransaction) GetAccount(idx int) (solana.PublicKey, error) {
if idx < len(vt.StaticAccountKeys) {
return vt.StaticAccountKeys[idx], nil
}
return solana.PublicKey{}, NewAccountNotFoundError(idx, len(vt.StaticAccountKeys))
}

View File

@@ -4,8 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/samlior/libsam/pkg/enum" "github.com/samlior/libsam/v2/pkg/enum"
"github.com/samlior/libsam/pkg/swqos/clients" "github.com/samlior/libsam/v2/pkg/swqos/clients"
) )
func NewSWQoSClient(ctx context.Context, config *SWQoSClientConfig) (SWQoSClient, error) { func NewSWQoSClient(ctx context.Context, config *SWQoSClientConfig) (SWQoSClient, error) {