Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b2a9af978 | ||
| cddfcc3eef | |||
| 6b35f61d95 | |||
| 2b93e7a5d4 | |||
|
|
edaed7d333 | ||
| 50d9212e2d | |||
| e90a2533fd | |||
| 84645d9c09 | |||
| b860ff1719 | |||
| c7e9354e07 | |||
| a1f9f4f9a9 | |||
| 21eba4264f | |||
| 17a3c9e89c | |||
| 8e734eb793 | |||
| 33c8a16015 | |||
| 62d48e5a22 | |||
|
|
3d2fd35344 | ||
| 32ba6da171 | |||
| 42631499c9 | |||
| 9a37e57473 | |||
|
|
e981df5f4b | ||
|
|
c20e019b43 | ||
|
|
c7be7cf4fd | ||
|
|
62c313d4a1 | ||
|
|
5fa6944a37 | ||
|
|
5d06d18aa8 | ||
| 9877794d1c | |||
| f6242f0193 | |||
| b06a1fa377 | |||
| 75c35f56f1 | |||
| 79859bc079 | |||
|
|
bd2dbe3c91 | ||
|
|
22d2df3782 | ||
| eb75bebbfd | |||
| db9e2b33cb | |||
|
|
f41e086028 | ||
| 77c8c0aad3 | |||
|
|
a0e46ec83e | ||
|
|
3324a71117 | ||
|
|
7557414fff | ||
|
|
eb394c5650 | ||
|
|
1223b34117 | ||
|
|
d866701679 | ||
| fa1875996c | |||
|
|
519b0ebb0b | ||
|
|
be86c888eb | ||
|
|
35c5c83f4b | ||
|
|
5f97972194 | ||
|
|
741d333e1b | ||
|
|
594c46a1d2 | ||
|
|
45107aa8c3 | ||
|
|
36db4729d4 | ||
| 23f37cff2c | |||
| 6bab10866b | |||
| 83aa772710 | |||
| da51b19b50 | |||
| f39b89b497 | |||
| 26e07ec52e | |||
| 35c57c3c7a | |||
| 3e58b62e1f | |||
|
|
4c0abc5c34 | ||
|
|
d9aea3e8d7 | ||
|
|
b82b7d9b0e | ||
| d9bc106eb1 | |||
| 871dac8bd3 | |||
|
|
156fd9b0bf | ||
|
|
2504636fb0 |
2
Makefile
2
Makefile
@@ -19,4 +19,4 @@ shreder:
|
||||
.PHONY: build
|
||||
# build
|
||||
build:
|
||||
mkdir -p bin/ && CGO_ENABLED=0 go build -o ./bin/ ./...
|
||||
mkdir -p bin/ && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/ ./...
|
||||
187
README.md
187
README.md
@@ -15,6 +15,8 @@ go get github.com/samlior/libsam
|
||||
| fra | fra1.shreder.xyz:9991 |
|
||||
| ams | ams1.shreder.xyz:9991 |
|
||||
| ewr | ny1.shreder.xyz:9991 |
|
||||
| uk | lon.shreder.xyz:9991 |
|
||||
| jp | tyo.shreder.xyz:9991 |
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -105,6 +107,13 @@ See [example](./cmd/shreder/main.go).
|
||||
"keepAliveUrl": "http://germany.solana.dex.blxrbdn.com/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "soyas",
|
||||
"sendTxUrl": "fra.landing.soyas.xyz:9000",
|
||||
"sendBundleUrl": "",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
}
|
||||
```
|
||||
|
||||
@@ -191,6 +200,13 @@ See [example](./cmd/shreder/main.go).
|
||||
"keepAliveUrl": "http://amsterdam.solana.dex.blxrbdn.com/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "soyas",
|
||||
"sendTxUrl": "ams.landing.soyas.xyz:9000",
|
||||
"sendBundleUrl": "",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
}
|
||||
```
|
||||
|
||||
@@ -278,6 +294,177 @@ See [example](./cmd/shreder/main.go).
|
||||
"keepAliveUrl": "http://ny.solana.dex.blxrbdn.com/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "soyas",
|
||||
"sendTxUrl": "nyc.landing.soyas.xyz:9000",
|
||||
"sendBundleUrl": "",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary> London </summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "helius",
|
||||
"sendTxUrl": "http://lon-sender.helius-rpc.com/fast",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://lon-sender.helius-rpc.com/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "blockrazor",
|
||||
"sendTxUrl": "london.solana-grpc.blockrazor.xyz:80",
|
||||
"sendBundleUrl": "",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "node1",
|
||||
"sendTxUrl": "http://lon.node1.me",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://lon.node1.me/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "nextblock",
|
||||
"sendTxUrl": "http://london.nextblock.io/api/v2/submit",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://london.nextblock.io/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
|
||||
},
|
||||
{
|
||||
"name": "flashBlock",
|
||||
"sendTxUrl": "http://london.flashblock.trade/api/v2/submit-batch",
|
||||
"sendBundleUrl": "http://london.flashblock.trade/api/v2/submit-batch",
|
||||
"keepAliveUrl": "http://london.flashblock.trade/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "stellium",
|
||||
"sendTxUrl": "http://lhr1.flashrpc.com/be95e80d-afc2-4a48-b017-db021fc4c19e",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://lhr1.flashrpc.com/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "blocxroute",
|
||||
"sendTxUrl": "http://uk.solana.dex.blxrbdn.com/api/v2/submit",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://uk.solana.dex.blxrbdn.com/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "soyas",
|
||||
"sendTxUrl": "lon.landing.soyas.xyz:9000",
|
||||
"sendBundleUrl": "",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details><summary> Japan </summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "helius",
|
||||
"sendTxUrl": "http://tyo-sender.helius-rpc.com/fast",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://tyo-sender.helius-rpc.com/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "0slot",
|
||||
"sendTxUrl": "http://jp1.0slot.trade?api-key=3fec78a0d361418a8eff95be9ed85cc3&anti-mev=true",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://jp1.0slot.trade/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "blockrazor",
|
||||
"sendTxUrl": "tokyo.solana-grpc.blockrazor.xyz:80",
|
||||
"sendBundleUrl": "",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "node1",
|
||||
"sendTxUrl": "http://tk.node1.me",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://tk.node1.me/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "nextblock",
|
||||
"sendTxUrl": "http://tokyo.nextblock.io/api/v2/submit",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://tokyo.nextblock.io/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 4
|
||||
},
|
||||
{
|
||||
"name": "flashBlock",
|
||||
"sendTxUrl": "http://tokyo.flashblock.trade/api/v2/submit-batch",
|
||||
"sendBundleUrl": "http://tokyo.flashblock.trade/api/v2/submit-batch",
|
||||
"keepAliveUrl": "http://tokyo.flashblock.trade/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "astralane",
|
||||
"sendTxUrl": "http://jp.gateway.astralane.io/iris?api-key=zhaozNc5OIadLPI3r9nUVVPpCZcQAUjngO6Tgr5XUJcmBrIisFaaZF81Ijn01Ytn",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://jp.gateway.astralane.io/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "nozomi",
|
||||
"sendTxUrl": "http://tyo1.nozomi.temporal.xyz/?c=34cff37e-f1a5-446a-98bb-66aa1b62cb74",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://tyo1.nozomi.temporal.xyz/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "stellium",
|
||||
"sendTxUrl": "http://tyo1.flashrpc.com/be95e80d-afc2-4a48-b017-db021fc4c19e",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://tyo1.flashrpc.com/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "blocxroute",
|
||||
"sendTxUrl": "http://tokyo.solana.dex.blxrbdn.com/api/v2/submit",
|
||||
"sendBundleUrl": "",
|
||||
"keepAliveUrl": "http://tokyo.solana.dex.blxrbdn.com/api/v2/ping",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
},
|
||||
{
|
||||
"name": "soyas",
|
||||
"sendTxUrl": "tyo.landing.soyas.xyz:9000",
|
||||
"sendBundleUrl": "",
|
||||
"tips": "0.001",
|
||||
"rateLimit": 0
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/gagliardetto/solana-go/programs/system"
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
"github.com/samlior/libsam/pkg/consts"
|
||||
"github.com/samlior/libsam/pkg/enum"
|
||||
"github.com/samlior/libsam/pkg/swqos"
|
||||
"github.com/samlior/libsam/v2/pkg/consts"
|
||||
"github.com/samlior/libsam/v2/pkg/enum"
|
||||
"github.com/samlior/libsam/v2/pkg/swqos"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
|
||||
"github.com/samlior/libsam/pkg/shreder"
|
||||
"github.com/samlior/libsam/v2/pkg/shreder"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -25,6 +25,7 @@ func main() {
|
||||
}
|
||||
rpcClient := rpc.New(rpcUrl)
|
||||
shreder.SetLogLevel(slog.LevelDebug)
|
||||
//handlers := shreder.GetRegisteredHandlers()
|
||||
shrederClient, cleanup, err := shreder.NewShrederClient(
|
||||
url,
|
||||
rpcClient,
|
||||
@@ -50,8 +51,19 @@ func main() {
|
||||
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
|
||||
},
|
||||
},
|
||||
"okxdexroutev2": {
|
||||
AccountRequired: []string{
|
||||
"proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u",
|
||||
},
|
||||
},
|
||||
|
||||
// TODO: axiom, gmgn, etc.
|
||||
})
|
||||
},
|
||||
//shreder.WithCustomParsers(map[solana.PublicKey]shreder.Handler{
|
||||
// solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"): handlers[solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")],
|
||||
// solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u"): handlers[solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u")],
|
||||
//}),
|
||||
shreder.BlocksStats(false), shreder.LogParsedStats(true), shreder.ShowTableLoaded(false))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -65,11 +77,10 @@ func main() {
|
||||
<-exitSignal
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// async read from shreder
|
||||
txCh := make(chan shreder.TxSignalBatch, 1000)
|
||||
txCh := make(chan shreder.TxSignal, 1000)
|
||||
go func() {
|
||||
err := shrederClient.ReadSync(ctx, txCh)
|
||||
err := shrederClient.ReadEntriesSync(ctx, txCh)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
panic(err)
|
||||
@@ -81,19 +92,10 @@ func main() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case txBatch := <-txCh:
|
||||
//jsonData, _ := json.MarshalIndent(txBatch, "", " ")
|
||||
for _, tx := range txBatch {
|
||||
if tx.Label == "flas" {
|
||||
if tx.Event == "buy" {
|
||||
fmt.Println("===============", tx.TxHash, tx.Program, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "sol:", tx.Token1Amount)
|
||||
} else if tx.Event == "sell" {
|
||||
fmt.Println("===============", tx.TxHash, tx.Program, tx.Event, tx.Token0Address, "token:", tx.Token0Amount)
|
||||
|
||||
case tx := <-txCh:
|
||||
if tx.Label == "dbot" || tx.Label == "okxdexroutev2" {
|
||||
fmt.Println("===============", tx.TxHash, tx.Label, tx.Program, tx.Event, tx.Token0Address, tx.Token1Address, "token0amount:", tx.Token0Amount, "token1amount:", tx.Token1Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart), "cu price:", tx.CUPrice, "swqos agent:", tx.SWQoSAgent, "swqos tips:", tx.SWQoSTips)
|
||||
}
|
||||
}
|
||||
}
|
||||
//fmt.Println(txBatch[0].TxHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
191
cmd/txparse/main.go
Normal file
191
cmd/txparse/main.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
addresslookuptable "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 = "4oSnHnDSscjmc6XX1rjXCEBavoLR9wkdZvGCAUn928iLWqrCwt2a6mgJpjP4NHqrCboUC82ugrjjEbNGNYAagkue"
|
||||
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,
|
||||
}
|
||||
}
|
||||
49
go.mod
49
go.mod
@@ -1,49 +1,50 @@
|
||||
module github.com/samlior/libsam
|
||||
module github.com/samlior/libsam/v2
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250722092120-44561cb37455
|
||||
github.com/gagliardetto/solana-go v1.12.0
|
||||
github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250908052524-06493dcc1bb4
|
||||
github.com/gagliardetto/binary v0.8.0
|
||||
github.com/gagliardetto/solana-go v1.14.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454
|
||||
github.com/panjf2000/ants/v2 v2.11.4
|
||||
github.com/quic-go/quic-go v0.59.0
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
google.golang.org/grpc v1.75.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
google.golang.org/grpc v1.78.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
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/blendle/zapdriver v1.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.9.0 // indirect
|
||||
github.com/gagliardetto/binary v0.8.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/gagliardetto/treeout v0.1.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/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/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.11 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // 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/reflect2 v1.0.2 // indirect
|
||||
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
|
||||
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.2 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
github.com/streamingfast/logging v0.0.0-20260108192805-38f96de0a641 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.7 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // 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
|
||||
)
|
||||
|
||||
144
go.sum
144
go.sum
@@ -1,26 +1,25 @@
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
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/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-20250722092120-44561cb37455/go.mod h1:vKj1SKlrekR9fuZgWQNNAWt/PUZIfzpGjDpIcbf1kT0=
|
||||
github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250908052524-06493dcc1bb4 h1:yvrhmN9vQIrquQP1fYul30khwfoE8oEL0VmwFZ37Mq8=
|
||||
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/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/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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
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/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/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY=
|
||||
github.com/gagliardetto/solana-go v1.12.0 h1:rzsbilDPj6p+/DOPXBMLhwMZeBgeRuXjm5zQFCoXgsg=
|
||||
github.com/gagliardetto/solana-go v1.12.0/go.mod h1:l/qqqIN6qJJPtxW/G1PF4JtcE3Zg2vD2EliZrr9Gn5k=
|
||||
github.com/gagliardetto/solana-go v1.14.0 h1:3WfAi70jOOjAJ0deFMjdhFYlLXATF4tOQXsDNWJtOLw=
|
||||
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/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok=
|
||||
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
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/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
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.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
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/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
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.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
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/go.mod h1:ye2e/VUEtE2BHE+G/QcKkcLQVAEJoYRFj5VUOQatCRE=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
@@ -73,125 +68,112 @@ 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/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
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.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
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-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/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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
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/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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws=
|
||||
go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.mongodb.org/mongo-driver v1.17.7 h1:a9w+U3Vt67eYzcfq3k/OAv284/uUUkL0uP75VE5rCOU=
|
||||
go.mongodb.org/mongo-driver v1.17.7/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
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/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.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/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
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.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/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
|
||||
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.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-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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
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.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-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-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-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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.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-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-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-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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
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.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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
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/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
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/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-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.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-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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
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-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package consts
|
||||
|
||||
import "github.com/samlior/libsam/pkg/enum"
|
||||
import "github.com/samlior/libsam/v2/pkg/enum"
|
||||
|
||||
var SWQoSFeeAddresses = map[string]string{
|
||||
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5": enum.SWQoSAgentJito,
|
||||
@@ -22,18 +22,8 @@ var SWQoSFeeAddresses = map[string]string{
|
||||
"Ey2JEr8hDkgN8qKJGrLf2yFjRhW7rab99HVxwi5rcvJE": enum.SWQoSAgent0slot,
|
||||
"4iUgjMT8q2hNZnLuhpqZ1QtiV8deFPy2ajvvjEpKKgsS": enum.SWQoSAgent0slot,
|
||||
"3Rz8uD83QsU8wKvZbgWAPvCNDU6Fy8TSZTMcPm3RB6zt": enum.SWQoSAgent0slot,
|
||||
"DiTmWENJsHQdawVUUKnUXkconcpW4Jv52TnMWhkncF6t": enum.SWQoSAgent0slot,
|
||||
"HRyRhQ86t3H4aAtgvHVpUJmw64BDrb61gRiKcdKUXs5c": enum.SWQoSAgent0slot,
|
||||
"7y4whZmw388w1ggjToDLSBLv47drw5SUXcLk6jtmwixd": enum.SWQoSAgent0slot,
|
||||
"J9BMEWFbCBEjtQ1fG5Lo9kouX1HfrKQxeUxetwXrifBw": enum.SWQoSAgent0slot,
|
||||
"8U1JPQh3mVQ4F5jwRdFTBzvNRQaYFQppHQYoH38DJGSQ": enum.SWQoSAgent0slot,
|
||||
"Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3": enum.SWQoSAgent0slot,
|
||||
"FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe": enum.SWQoSAgent0slot,
|
||||
"ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13": enum.SWQoSAgent0slot,
|
||||
"6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK": enum.SWQoSAgent0slot,
|
||||
"Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr": enum.SWQoSAgent0slot,
|
||||
"HWEoBxYs7ssKuudEjzjmpfJVX7Dvi7wescFsVx2L5yoY": enum.SWQoSAgentBlocxRoute,
|
||||
"HZTmLyC683y74TW3HtGbNX5orxjm2sPuZBEYwwSgAM8v": enum.SWQoSAgentBlocxRoute,
|
||||
"95cfoy472fcQHaw4tPGBTKpn6ZQnfEPfBgDQx6gcRmRg": enum.SWQoSAgentBlocxRoute,
|
||||
"FogxVNs6Mm2w9rnGL1vkARSwJxvLE8mujTv3LK8RnUhF": enum.SWQoSAgentBlocxRoute,
|
||||
"3UQUKjhMKaY2S6bjcQD6yHB7utcZt5bfarRCmctpRtUd": enum.SWQoSAgentBlocxRoute,
|
||||
"TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq": enum.SWQoSAgentNozomi,
|
||||
@@ -77,13 +67,6 @@ var SWQoSFeeAddresses = map[string]string{
|
||||
"node1PUber6SFmSQgvf2ECmXsHP5o3boRSGhvJyPMX1": enum.SWQoSAgentNode1,
|
||||
"node1AyMbeqiVN6eoQzEAwCA6Pk826hrdqdAHR7cdJ3": enum.SWQoSAgentNode1,
|
||||
"node1YtWCoTwwVYTFLfS19zquRQzYX332hs1HEuRBjC": enum.SWQoSAgentNode1,
|
||||
"node1EoLojAvoUmyDytcvgdXs6GPtY3zpQXPCRVncEA": enum.SWQoSAgentNode1,
|
||||
"node1CVxtFas2Pw5Vcf86Pq89Hqx4jveo1ntY7ARFMK": enum.SWQoSAgentNode1,
|
||||
"node1E3hguapYA18HCpEEkRHQmLNiyv9pdfE9s2zo5X": enum.SWQoSAgentNode1,
|
||||
"node1zrVjcY2XB3Au8qYj5MxjbNfGu3baHaqZMkPM7Z": enum.SWQoSAgentNode1,
|
||||
"node1FdMPnJBN7QTuhzNw3VS823nxFuDTizrrbcEqzp": enum.SWQoSAgentNode1,
|
||||
"node1VwH169UqyJHr5MYCH3EBuwrdvn5KHXAkhEEfav": enum.SWQoSAgentNode1,
|
||||
"node1L7Xat2tSkRNNi6TSuUScMYfj64ovhr2aceJm9g": enum.SWQoSAgentNode1,
|
||||
"FLasHstqx11M8W56zrSEqkCyhMCCpr6ze6Mjdvqope5s": enum.SWQoSAgentFlashBlock,
|
||||
"FLasHXTqrbNvpWFB6grN47HGZfK6pze9HLNTgbukfPSk": enum.SWQoSAgentFlashBlock,
|
||||
"FLashhsorBmM9dLpuq6qATawcpqk1Y2aqaZfkd48iT3W": enum.SWQoSAgentFlashBlock,
|
||||
@@ -108,6 +91,15 @@ var SWQoSFeeAddresses = map[string]string{
|
||||
"BnGKHAC386n4Qmv9xtpBVbRaUTKixjBe3oagkPFKtoy6": enum.SWQoSAgentBlockRazor,
|
||||
"Dd7K2Fp7AtoN8xCghKDRmyqr5U169t48Tw5fEd3wT9mq": enum.SWQoSAgentBlockRazor,
|
||||
"AP6qExwrbRgBAVaehg4b5xHENX815sMabtBzUzVB4v8S": enum.SWQoSAgentBlockRazor,
|
||||
"soyas4s6L8KWZ8rsSk1mF3d1mQScoTGGAgjk98bF8nP": enum.SWQoSAgentSoyas,
|
||||
"soyascXFW5wEEYiwfEmHy2pNwomqzvggJosGVD6TJdY": enum.SWQoSAgentSoyas,
|
||||
"soyasDBdKjADwPz3xk82U3TNPRDKEWJj7wWLajNHZ1L": enum.SWQoSAgentSoyas,
|
||||
"soyasE2abjBAynmHbGWgEwk4ctBy7JMTUCNrMbjcnyH": enum.SWQoSAgentSoyas,
|
||||
"ste11JV3MLMM7x7EJUM2sXcJC1H7F4jBLnP9a9PG8PH": enum.SWQoSAgentStellium,
|
||||
"ste11MWPjXCRfQryCshzi86SGhuXjF4Lv6xMXD2AoSt": enum.SWQoSAgentStellium,
|
||||
"ste11p5x8tJ53H1NbNQsRBg1YNRd4GcVpxtDw8PBpmb": enum.SWQoSAgentStellium,
|
||||
"ste11p7e2KLYou5bwtt35H7BM6uMdo4pvioGjJXKFcN": enum.SWQoSAgentStellium,
|
||||
"ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX": enum.SWQoSAgentStellium,
|
||||
"astrazznxsGUhWShqgNtAdfrzP2G83DzcWVJDxwV9bF": enum.SWQoSAgentAstralane,
|
||||
"astra4uejePWneqNaJKuFFA8oonqCE1sqF6b45kDMZm": enum.SWQoSAgentAstralane,
|
||||
"astra9xWY93QyfG6yM8zwsKsRodscjQ2uU2HKNL5prk": enum.SWQoSAgentAstralane,
|
||||
@@ -116,9 +108,12 @@ var SWQoSFeeAddresses = map[string]string{
|
||||
"astraubkDw81n4LuutzSQ8uzHCv4BhPVhfvTcYv8SKC": enum.SWQoSAgentAstralane,
|
||||
"astraZW5GLFefxNPAatceHhYjfA1ciq9gvfEg2S47xk": enum.SWQoSAgentAstralane,
|
||||
"astrawVNP4xDBKT7rAdxrLYiTSTdqtUr63fSMduivXK": enum.SWQoSAgentAstralane,
|
||||
"ste11JV3MLMM7x7EJUM2sXcJC1H7F4jBLnP9a9PG8PH": enum.SWQoSAgentStellium,
|
||||
"ste11MWPjXCRfQryCshzi86SGhuXjF4Lv6xMXD2AoSt": enum.SWQoSAgentStellium,
|
||||
"ste11p5x8tJ53H1NbNQsRBg1YNRd4GcVpxtDw8PBpmb": enum.SWQoSAgentStellium,
|
||||
"ste11p7e2KLYou5bwtt35H7BM6uMdo4pvioGjJXKFcN": enum.SWQoSAgentStellium,
|
||||
"ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX": enum.SWQoSAgentStellium,
|
||||
"FAST3dMFZvESiEipBvLSiXq3QCV51o3xuoHScqRU6cB6": enum.SWQoSAgentFast,
|
||||
"FASTCKnwwY6iL3CknRgg3Zqir7jeagDDhxSnBQQy5a1C": enum.SWQoSAgentFast,
|
||||
"FASTHPW6akdGh9PFSdhMTbCuGkCSX7LsUjjnaB2RTQ4v": enum.SWQoSAgentFast,
|
||||
"FASTKL1AamNKrwnvbKwo4PU8434BBdqVrTtugM6oDU71": enum.SWQoSAgentFast,
|
||||
"FASTPB76TxKPMZ7Q29m8v4zJn8gUjbWyvTEQaaxhwN7M": enum.SWQoSAgentFast,
|
||||
"FASTYKWXRfAoty7SQCM1mGVrmPUyyNcF4tc3DUkLDAu9": enum.SWQoSAgentFast,
|
||||
"FASTYmSidNfLwdwiQEhCTtzghxEtaipeNSDSwh9xDPs3": enum.SWQoSAgentFast,
|
||||
"FASTs6ctgbsuZegMzUs4DPUYhRSZUPCjgCVnttHbpQAp": enum.SWQoSAgentFast,
|
||||
}
|
||||
|
||||
271
pkg/consts/swqos_fee_addresses2.go
Normal file
271
pkg/consts/swqos_fee_addresses2.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package consts
|
||||
|
||||
import (
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/samlior/libsam/v2/pkg/enum"
|
||||
)
|
||||
|
||||
// NOTE:
|
||||
// SWQoSFeeAddresses2 专门用于解析交易使用了什么 swqos agent, 不是用来发交易的
|
||||
// 和 SWQoSFeeAddresses 主要区别在于, 这里包含一些隐藏地址, 这些隐藏地址是某些平台专属的
|
||||
// 我们用这些专属的地址发交易会被拒绝
|
||||
var SWQoSFeeAddresses2 = map[string]string{
|
||||
"3Rz8uD83QsU8wKvZbgWAPvCNDU6Fy8TSZTMcPm3RB6zt": enum.SWQoSAgent0slot,
|
||||
"4HiwLEP2Bzqj3hM2ENxJuzhcPCdsafwiet3oGkMkuQY4": enum.SWQoSAgent0slot,
|
||||
"4iUgjMT8q2hNZnLuhpqZ1QtiV8deFPy2ajvvjEpKKgsS": enum.SWQoSAgent0slot,
|
||||
"6SiVU5WEwqfFapRuYCndomztEwDjvS5xgtEof3PLEGm9": enum.SWQoSAgent0slot,
|
||||
"6fQaVhYZA4w3MBSXjJ81Vf6W1EDYeUPXpgVQ6UQyU1Av": enum.SWQoSAgent0slot,
|
||||
"6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK": enum.SWQoSAgent0slot,
|
||||
"7toBU3inhmrARGngC7z6SjyP85HgGMmCTEwGNRAcYnEK": enum.SWQoSAgent0slot,
|
||||
"7y4whZmw388w1ggjToDLSBLv47drw5SUXcLk6jtmwixd": enum.SWQoSAgent0slot,
|
||||
"8U1JPQh3mVQ4F5jwRdFTBzvNRQaYFQppHQYoH38DJGSQ": enum.SWQoSAgent0slot,
|
||||
"8mR3wB1nh4D6J9RUCugxUpc6ya8w38LPxZ3ZjcBhgzws": enum.SWQoSAgent0slot,
|
||||
"Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr": enum.SWQoSAgent0slot,
|
||||
"D8f3WkQu6dCF33cZxuAsrKHrGsqGP2yvAHf8mX6RXnwf": enum.SWQoSAgent0slot,
|
||||
"DiTmWENJsHQdawVUUKnUXkconcpW4Jv52TnMWhkncF6t": enum.SWQoSAgent0slot,
|
||||
"ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13": enum.SWQoSAgent0slot,
|
||||
"Eb2KpSC8uMt9GmzyAEm5Eb1AAAgTjRaXWFjKyFXHZxF3": enum.SWQoSAgent0slot,
|
||||
"Ey2JEr8hDkgN8qKJGrLf2yFjRhW7rab99HVxwi5rcvJE": enum.SWQoSAgent0slot,
|
||||
"FCjUJZ1qozm1e8romw216qyfQMaaWKxWsuySnumVCCNe": enum.SWQoSAgent0slot,
|
||||
"GQPFicsy3P3NXxB5piJohoxACqTvWE9fKpLgdsMduoHE": enum.SWQoSAgent0slot,
|
||||
"HRyRhQ86t3H4aAtgvHVpUJmw64BDrb61gRiKcdKUXs5c": enum.SWQoSAgent0slot,
|
||||
"J9BMEWFbCBEjtQ1fG5Lo9kouX1HfrKQxeUxetwXrifBw": enum.SWQoSAgent0slot,
|
||||
"TpdxgNJBWZRL8UXF5mrEsyWxDWx9HQexA9P1eTWQ42p": enum.SWQoSAgent0slot,
|
||||
"Gu2UGEfze3Gg5cHuEC4jGbyCufgpev75RkVvBdKKtf12": enum.SWQoSAgent0slot,
|
||||
"E8wD3SMD1trozPrvSN9F6SyuUXD7rrFDuR3WexGziKG5": enum.SWQoSAgent0slot,
|
||||
"18hCV7f9CPmZRAH3QCNZaGHhHeNSfisQKeKuFkQsPLY": enum.SWQoSAgent0slot,
|
||||
"2sYKRWBNVY6UomMBi4juoMrrL98bqizDMn98cJ3cBmye": enum.SWQoSAgent0slot,
|
||||
"CZubxabMM7CPFSDAfMUhxNuvXRDLjDf6yVVq1RoJ66rk": enum.SWQoSAgent0slot,
|
||||
"Dz8rMcdokTLfbnNz2ZdYocZixgaA1TMqbA31xtwPgcxb": enum.SWQoSAgent0slot,
|
||||
"ForLDu55GfA2U1aTUaitmjzjs92vvVn1MSqzY3D9HtAK": enum.SWQoSAgent0slot,
|
||||
"6MgjyQU7G988jgL6EGAgfHYoeesCnwYMyPeh1fpJ71FP": enum.SWQoSAgent0slot,
|
||||
"12pHu2j2DDShyCVFU7vtSLXga74et9y83VD38mw6XYhB": enum.SWQoSAgent0slot,
|
||||
"5QuV4TS5TJFWPu7Yd56VaPvf4nKUicPvTfC3mwnb7dNW": enum.SWQoSAgent0slot,
|
||||
"4gh9m7RV7G4WwRftA6qV7RhDfytdepb3XbxFRfTtneYJ": enum.SWQoSAgent0slot,
|
||||
"AumQWSLrWwDXRq1yDEYPiw8vT5NUBYzrbdWCprJ4ZUa8": enum.SWQoSAgent0slot,
|
||||
"3vGEsQA5jzvN8TBgytuYEdZxW6P2pK1c6pq56JiFuygS": enum.SWQoSAgent0slot,
|
||||
"AsEF2SWSEZ1xpGZ5fdzDKaoka1XEtFSjGo39YUXkpvAh": enum.SWQoSAgent0slot,
|
||||
"2WoQNgmc4SEXrR3rKQypmeWmsxGqHHE6rApnVrP6Pt77": enum.SWQoSAgent0slot,
|
||||
"astra4uejePWneqNaJKuFFA8oonqCE1sqF6b45kDMZm": enum.SWQoSAgentAstralane,
|
||||
"astra9xWY93QyfG6yM8zwsKsRodscjQ2uU2HKNL5prk": enum.SWQoSAgentAstralane,
|
||||
"astraRVUuTHjpwEVvNBeQEgwYx9w9CFyfxjYoobCZhL": enum.SWQoSAgentAstralane,
|
||||
"astrazznxsGUhWShqgNtAdfrzP2G83DzcWVJDxwV9bF": enum.SWQoSAgentAstralane,
|
||||
"ASde6y8pBCU1aityWHRpqT7pEAcEonjCgFUMeh5egRes": enum.SWQoSAgentAstralane,
|
||||
"ASUv6G8Cj6zt71UAqD1aVtDC3CRn6FFddqF17ZiegrES": enum.SWQoSAgentAstralane,
|
||||
"ASY4mvCtrACKFK8Jiuvqcu8fad9gGTzvfm5zp4megRes": enum.SWQoSAgentAstralane,
|
||||
"astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK": enum.SWQoSAgentAstralane,
|
||||
"B1ooMsWjc4SUVVuLyCu1ig2RdomQnHKgMzBMfmSo3DK": enum.SWQoSAgentAstralane,
|
||||
"B1ooMZfUJmAvppzc5cr7eYG8Cenig4FbQGBytr4DGCh": enum.SWQoSAgentAstralane,
|
||||
"b1ooMDLjzz4QqecNsJ8bBXzJTzfAPDCP3CxijTS2K93": enum.SWQoSAgentAstralane,
|
||||
"b1oomst2baE3FqxFPHaA9JwhXgFG9HdTLmbNKDen1kK": enum.SWQoSAgentAstralane,
|
||||
"b1ooMngj7WbNPMZpWpnYRjxQ96RcDZ9ZFpRfjw1g7tg": enum.SWQoSAgentAstralane,
|
||||
"B1oomgV9SAeiUc7GMEg9WhqkZJGccJuHAnh15DbezcN": enum.SWQoSAgentAstralane,
|
||||
"b1oom3jaRNoyJzvSdSVbvSbth5uB4rRYtbjHXT5c1eW": enum.SWQoSAgentAstralane,
|
||||
"B1ooMauwuJPhHsXqt3uj7B92CAFG8kaD1Q2iGEmGYnx": enum.SWQoSAgentAstralane,
|
||||
"B1ooMdjcY7zemxDWiH8jVZPxEMdHnE5AraWPHdHQoPj": enum.SWQoSAgentAstralane,
|
||||
"B1ooMKzu6siJzQutP6a6oLiY3fpzgQnBZsAjxuAm9qo": enum.SWQoSAgentAstralane,
|
||||
"3UQUKjhMKaY2S6bjcQD6yHB7utcZt5bfarRCmctpRtUd": enum.SWQoSAgentBlocxRoute,
|
||||
"9vTpfGYN2jtjZgXQ7gihyHmN3FseLP7uW1CWMdsgcny": enum.SWQoSAgentBlocxRoute,
|
||||
"7ks326H4LbMVaUC8nW5FpC5EoAf5eK5pf4Dsx4HDQLpq": enum.SWQoSAgentBlocxRoute,
|
||||
"95cfoy472fcQHaw4tPGBTKpn6ZQnfEPfBgDQx6gcRmRg": enum.SWQoSAgentBlocxRoute,
|
||||
"FogxVNs6Mm2w9rnGL1vkARSwJxvLE8mujTv3LK8RnUhF": enum.SWQoSAgentBlocxRoute,
|
||||
"HWEoBxYs7ssKuudEjzjmpfJVX7Dvi7wescFsVx2L5yoY": enum.SWQoSAgentBlocxRoute,
|
||||
"HZTmLyC683y74TW3HtGbNX5orxjm2sPuZBEYwwSgAM8v": enum.SWQoSAgentBlocxRoute,
|
||||
"6GZVKMaoWry4UFiydjeQU9nmAxj3hEARAStQ7Hc2z6TB": enum.SWQoSAgentBlocxRoute,
|
||||
"CyL8mfycXYbWHVoTTsfvnAfF2MvfcqeQAmmsqNQLxF7g": enum.SWQoSAgentBlockRazor,
|
||||
"Eg85QSYLwtZfBBPF4CsNmijJDXUAeCMjoh36L1cwboqg": enum.SWQoSAgentBlockRazor,
|
||||
"9gBzvLKedrs9HxaLPhBdkPaeFTxEDNDGfqJmqvHjfiZp": enum.SWQoSAgentBlockRazor,
|
||||
"7BxoFqM3swL46Lt9EWzL9z2LeXYfmJL7MVzpFrDpLPei": enum.SWQoSAgentBlockRazor,
|
||||
"91Ht2gq1CMPcLySuq8NjHaA1rXysm8zzoiiyfT4uSE7u": enum.SWQoSAgentBlockRazor,
|
||||
"2zCYpNSWcHX9AzFndF1mcT1bMkG1EXMzzjFcBjSnJq9f": enum.SWQoSAgentBlockRazor,
|
||||
"4Kfqkx3c8TxLX74J1nzfzfHCGdoDCuZ8k84sGpnVh1a4": enum.SWQoSAgentBlockRazor,
|
||||
"GeiVfSfUBVxjJA6F2SNSASoK8JaSCiSmsC2hBrPLfpiv": enum.SWQoSAgentBlockRazor,
|
||||
"DggsS83MWeUHZdrV2jyMUh8GDfLrU5P9Es36h7Uf3wRp": enum.SWQoSAgentBlockRazor,
|
||||
"2d5viHZBHKt5DgEpMckXEfndR1CoZ1tHvcbL9fU4xqT7": enum.SWQoSAgentBlockRazor,
|
||||
"73VnqgMJq29j4HMzF6GRdBeVpZgz7ibouyKQvyAKbVZy": enum.SWQoSAgentBlockRazor,
|
||||
"HvgA9hTyrTQCU5869fhZ7My9WkkHK2yBo4Wu6ojHmMio": enum.SWQoSAgentBlockRazor,
|
||||
"68Pwb4jS7eZATjDfhmTXgRJjCiZmw1L7Huy4HNpnxJ3o": enum.SWQoSAgentBlockRazor,
|
||||
"4ABhJh5rZPjv63RBJBuyWzBK3g9gWMUQdTZP2kiW31V9": enum.SWQoSAgentBlockRazor,
|
||||
"5jA59cXMKQqZAVdtopv8q3yyw9SYfiE3vUCbt7p8MfVf": enum.SWQoSAgentBlockRazor,
|
||||
"5YktoWygr1Bp9wiS1xtMtUki1PeYuuzuCF98tqwYxf61": enum.SWQoSAgentBlockRazor,
|
||||
"295Avbam4qGShBYK7E9H5Ldew4B3WyJGmgmXfiWdeeyV": enum.SWQoSAgentBlockRazor,
|
||||
"EDi4rSy2LZgKJX74mbLTFk4mxoTgT6F7HxxzG2HBAFyK": enum.SWQoSAgentBlockRazor,
|
||||
"BnGKHAC386n4Qmv9xtpBVbRaUTKixjBe3oagkPFKtoy6": enum.SWQoSAgentBlockRazor,
|
||||
"Dd7K2Fp7AtoN8xCghKDRmyqr5U169t48Tw5fEd3wT9mq": enum.SWQoSAgentBlockRazor,
|
||||
"A9cWowVAiHe9pJfKAj3TJiN9VpbzMUq6E4kEvf5mUT22": enum.SWQoSAgentBlockRazor,
|
||||
"6No2i3aawzHsjtThw81iq1EXPJN6rh8eSJCLaYZfKDTG": enum.SWQoSAgentBlockRazor,
|
||||
"FjmZZrFvhnqqb9ThCuMVnENaM3JGVuGWNyCAxRJcFpg9": enum.SWQoSAgentBlockRazor,
|
||||
"4xJEQnuMpoUNxhNew4AechRBo1DnpVfLyUe68BXTTF73": enum.SWQoSAgentBlockRazor,
|
||||
"Gywj98ophM7GmkDdaWs4isqZnDdFCW7B46TXmKfvyqSm": enum.SWQoSAgentBlockRazor,
|
||||
"7ZKL8BAPfKKa6FNmds48QKFnckrcj4mkppRnsBAR2xVH": enum.SWQoSAgentBlockRazor,
|
||||
"BYpBPSRkVSvutxHngtxnqeoTBrENZ8iM56Ywnsmy829w": enum.SWQoSAgentBlockRazor,
|
||||
"4LEkLhb2u5qCUXS1Hc3eL2zTxk2kjSzQeFK4ZgWsV3EM": enum.SWQoSAgentBlockRazor,
|
||||
"AP6qExwrbRgBAVaehg4b5xHENX815sMabtBzUzVB4v8S": enum.SWQoSAgentBlockRazor,
|
||||
"B2M4NG5eyZp5SBQrSdtemzk5TqVuaWGQnowGaCBt8GyM": enum.SWQoSAgentBlockRazor,
|
||||
"96Zc2GT7ZmMvF7rXgcwHAyJ7KmK8RaS4Z3VZw2b7GjJx": enum.SWQoSAgentBlockRazor,
|
||||
"9Kaz3Q9KJ3x8SXvui37FK5m1AwcwqkYLvS9Xg1Why9Q1": enum.SWQoSAgentBlockRazor,
|
||||
"muV321VhQ4XgJkVtsZP13zbCqg9HokT222bWS3DBxp3": enum.SWQoSAgentBlockRazor,
|
||||
"Hth7qf5dv683k3ZJffjJvJ8gSU21dfPWy3mBEyRRhCiN": enum.SWQoSAgentBlockRazor,
|
||||
"pD7KfmGkxHqQFNLqYv3zshSzkGaAB99vjNDKz6e7nGC": enum.SWQoSAgentBlockRazor,
|
||||
"AQChXZ1ZWvPH8EjdPxXXsC8VqCaBmPVruJbswhE3xNZ8": enum.SWQoSAgentBlockRazor,
|
||||
"2L2DgQ5ZXRYnv8K97NFDJvsNrA1MsrCGr3CvokPtDy8D": enum.SWQoSAgentBlockRazor,
|
||||
"4BjQeBGZmGNWeHfQC4scHK5d4RtDr79h1hZNPcrLDS8C": enum.SWQoSAgentBlockRazor,
|
||||
"E99TTcqBPAY1F4ZppMRkDX3pTqaSnRC24tUErfd2opNL": enum.SWQoSAgentBlockRazor,
|
||||
"8zi6AG7oSKoswSEMaxNmXrwBYmDwuQ4GLiY4Q1j9Rayu": enum.SWQoSAgentBlockRazor,
|
||||
"8GgU7tKJSA97G97kD9AbxYgsC9Hcjfg7RpAofWuA6oHt": enum.SWQoSAgentBlockRazor,
|
||||
"JDxQoXGFRwEojWzkirDNeHz88SDEPzdDakjsobJ4YHrj": enum.SWQoSAgentBlockRazor,
|
||||
"GQgHdPuDNcss3BoKrMfS6bgGekjitmKQRJxnuhUBu921": enum.SWQoSAgentBlockRazor,
|
||||
"8FoxPbnucCZ3wuzhMofKE5VdYKcHfWmYNrnC2whVBAhS": enum.SWQoSAgentBlockRazor,
|
||||
"3L9UZWLAprLtB2xddEHsCmgXbPc2PidgSjtHGZd2MzB3": enum.SWQoSAgentBlockRazor,
|
||||
"FAST3dMFZvESiEipBvLSiXq3QCV51o3xuoHScqRU6cB6": enum.SWQoSAgentFast,
|
||||
"FASTCKnwwY6iL3CknRgg3Zqir7jeagDDhxSnBQQy5a1C": enum.SWQoSAgentFast,
|
||||
"FASTHPW6akdGh9PFSdhMTbCuGkCSX7LsUjjnaB2RTQ4v": enum.SWQoSAgentFast,
|
||||
"FASTKL1AamNKrwnvbKwo4PU8434BBdqVrTtugM6oDU71": enum.SWQoSAgentFast,
|
||||
"FASTPB76TxKPMZ7Q29m8v4zJn8gUjbWyvTEQaaxhwN7M": enum.SWQoSAgentFast,
|
||||
"FASTYKWXRfAoty7SQCM1mGVrmPUyyNcF4tc3DUkLDAu9": enum.SWQoSAgentFast,
|
||||
"FASTYmSidNfLwdwiQEhCTtzghxEtaipeNSDSwh9xDPs3": enum.SWQoSAgentFast,
|
||||
"FASTs6ctgbsuZegMzUs4DPUYhRSZUPCjgCVnttHbpQAp": enum.SWQoSAgentFast,
|
||||
"FLASHRzANfcAKDuQ3RXv9hbkBy4WVEKDzoAgxJ56DiE4": enum.SWQoSAgentFlashBlock,
|
||||
"FLAShWTjcweNT4NSotpjpxAkwxUr2we3eXQGhpTVzRwy": enum.SWQoSAgentFlashBlock,
|
||||
"FLAsHZTRcf3Dy1APaz6j74ebdMC6Xx4g6i9YxjyrDybR": enum.SWQoSAgentFlashBlock,
|
||||
"FLAshyAyBcKb39KPxSzXcepiS8iDYUhDGwJcJDPX4g2B": enum.SWQoSAgentFlashBlock,
|
||||
"FLaSHJNm5dWYzEgnHJWWJP5ccu128Mu61NJLxUf7mUXU": enum.SWQoSAgentFlashBlock,
|
||||
"FLaSHR4Vv7sttd6TyDF4yR1bJyAxRwWKbohDytEMu3wL": enum.SWQoSAgentFlashBlock,
|
||||
"FLaShB3iXXTWE1vu9wQsChUKq3HFtpMAhb8kAh1pf1wi": enum.SWQoSAgentFlashBlock,
|
||||
"FLasHXTqrbNvpWFB6grN47HGZfK6pze9HLNTgbukfPSk": enum.SWQoSAgentFlashBlock,
|
||||
"FLasHstqx11M8W56zrSEqkCyhMCCpr6ze6Mjdvqope5s": enum.SWQoSAgentFlashBlock,
|
||||
"FLashhsorBmM9dLpuq6qATawcpqk1Y2aqaZfkd48iT3W": enum.SWQoSAgentFlashBlock,
|
||||
"2nyhqdwKcJZR2vcqCyrYsaPVdAnFoJjiksCXJ7hfEYgD": enum.SWQoSAgentHelius,
|
||||
"2q5pghRs6arqVjRvT5gfgWfWcHWmw1ZuCzphgd5KfWGJ": enum.SWQoSAgentHelius,
|
||||
"3KCKozbAaF75qEU33jtzozcJ29yJuaLJTy2jFdzUY8bT": enum.SWQoSAgentHelius,
|
||||
"4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE": enum.SWQoSAgentHelius,
|
||||
"4TQLFNWK8AovT1gFvda5jfw2oJeRMKEmw7aH6MGBJ3or": enum.SWQoSAgentHelius,
|
||||
"4vieeGHPYPG2MmyPRcYjdiDmmhN3ww7hsFNap8pVN3Ey": enum.SWQoSAgentHelius,
|
||||
"5VY91ws6B2hMmBFRsXkoAAdsPHBJwRfBht4DXox3xkwn": enum.SWQoSAgentHelius,
|
||||
"9bnz4RShgq1hAnLnZbP8kbgBg1kEmcJBYQq3gQbmnSta": enum.SWQoSAgentHelius,
|
||||
"D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ": enum.SWQoSAgentHelius,
|
||||
"wyvPkWjVZz1M8fHQnMMCDTQDbkManefNNhweYk5WkcF": enum.SWQoSAgentHelius,
|
||||
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT": enum.SWQoSAgentJito,
|
||||
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5": enum.SWQoSAgentJito,
|
||||
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49": enum.SWQoSAgentJito,
|
||||
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt": enum.SWQoSAgentJito,
|
||||
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY": enum.SWQoSAgentJito,
|
||||
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh": enum.SWQoSAgentJito,
|
||||
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL": enum.SWQoSAgentJito,
|
||||
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe": enum.SWQoSAgentJito,
|
||||
"NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid": enum.SWQoSAgentNextBlock,
|
||||
"NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X": enum.SWQoSAgentNextBlock,
|
||||
"NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb": enum.SWQoSAgentNextBlock,
|
||||
"NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2": enum.SWQoSAgentNextBlock,
|
||||
"NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE": enum.SWQoSAgentNextBlock,
|
||||
"nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG": enum.SWQoSAgentNextBlock,
|
||||
"neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At": enum.SWQoSAgentNextBlock,
|
||||
"nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc": enum.SWQoSAgentNextBlock,
|
||||
"ste11JV3MLMM7x7EJUM2sXcJC1H7F4jBLnP9a9PG8PH": enum.SWQoSAgentStellium,
|
||||
"ste11MWPjXCRfQryCshzi86SGhuXjF4Lv6xMXD2AoSt": enum.SWQoSAgentStellium,
|
||||
"ste11p5x8tJ53H1NbNQsRBg1YNRd4GcVpxtDw8PBpmb": enum.SWQoSAgentStellium,
|
||||
"ste11p7e2KLYou5bwtt35H7BM6uMdo4pvioGjJXKFcN": enum.SWQoSAgentStellium,
|
||||
"ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX": enum.SWQoSAgentStellium,
|
||||
"soyas4s6L8KWZ8rsSk1mF3d1mQScoTGGAgjk98bF8nP": enum.SWQoSAgentSoyas,
|
||||
"soyascXFW5wEEYiwfEmHy2pNwomqzvggJosGVD6TJdY": enum.SWQoSAgentSoyas,
|
||||
"soyasDBdKjADwPz3xk82U3TNPRDKEWJj7wWLajNHZ1L": enum.SWQoSAgentSoyas,
|
||||
"soyasE2abjBAynmHbGWgEwk4ctBy7JMTUCNrMbjcnyH": enum.SWQoSAgentSoyas,
|
||||
"node1FdMPnJBN7QTuhzNw3VS823nxFuDTizrrbcEqzp": enum.SWQoSAgentNode1,
|
||||
"node1zrVjcY2XB3Au8qYj5MxjbNfGu3baHaqZMkPM7Z": enum.SWQoSAgentNode1,
|
||||
"node1E3hguapYA18HCpEEkRHQmLNiyv9pdfE9s2zo5X": enum.SWQoSAgentNode1,
|
||||
"node1CVxtFas2Pw5Vcf86Pq89Hqx4jveo1ntY7ARFMK": enum.SWQoSAgentNode1,
|
||||
"node1EoLojAvoUmyDytcvgdXs6GPtY3zpQXPCRVncEA": enum.SWQoSAgentNode1,
|
||||
"node1VwH169UqyJHr5MYCH3EBuwrdvn5KHXAkhEEfav": enum.SWQoSAgentNode1,
|
||||
"node1JkDqyiEg7CDNj3ATPiRmWaAG2gnrAEiMJ4Rzcc": enum.SWQoSAgentNode1,
|
||||
"node1GS8pZnP6MzGSXwhA2MXH6EBfCpFaAE64G2ubpB": enum.SWQoSAgentNode1,
|
||||
"node1AVfbcSi98LAgGyAHUGS4eYkYTbS5vUPZYQnViF": enum.SWQoSAgentNode1,
|
||||
"node1L7Xat2tSkRNNi6TSuUScMYfj64ovhr2aceJm9g": enum.SWQoSAgentNode1,
|
||||
"node1kMY97W3LPXaKKV43yRa2Q3BLg4WZiT27VifUDc": enum.SWQoSAgentNode1,
|
||||
"node1Zi3r7hmGYwF9cJAkfCHh9EKWbkSrYdvcvLukF4": enum.SWQoSAgentNode1,
|
||||
"node1G3fmoCuEJzcPNF4hLbSZ2ypcUuh9CB3k9E7Q8k": enum.SWQoSAgentNode1,
|
||||
"node18nQgpjoKe1fM72GiV6tHXg5dMKbVPFGwRBD9MU": enum.SWQoSAgentNode1,
|
||||
"node1spgxXR8HCbm4LyZNoisFLmBXxy2qnZrv63WxMp": enum.SWQoSAgentNode1,
|
||||
"node1rmmFXeLh94mBGtDHbSwCrBJqDnc16xrURHRYD9": enum.SWQoSAgentNode1,
|
||||
"node1PqAa3BWWzUnTHVbw8NJHC874zn9ngAkXjgWEej": enum.SWQoSAgentNode1,
|
||||
"node1UzzTxAAeBTpfZkQPJXBAqixsbdth11ba1NXLBG": enum.SWQoSAgentNode1,
|
||||
"node1Qm1bV4fwYnCurP8otJ9s5yrkPq7SPZ5uhj3Tsv": enum.SWQoSAgentNode1,
|
||||
"node1PUber6SFmSQgvf2ECmXsHP5o3boRSGhvJyPMX1": enum.SWQoSAgentNode1,
|
||||
"node1AyMbeqiVN6eoQzEAwCA6Pk826hrdqdAHR7cdJ3": enum.SWQoSAgentNode1,
|
||||
"node1YtWCoTwwVYTFLfS19zquRQzYX332hs1HEuRBjC": enum.SWQoSAgentNode1,
|
||||
"TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq": enum.SWQoSAgentNozomi,
|
||||
"noz3jAjPiHuBPqiSPkkugaJDkJscPuRhYnSpbi8UvC4": enum.SWQoSAgentNozomi,
|
||||
"noz3str9KXfpKknefHji8L1mPgimezaiUyCHYMDv1GE": enum.SWQoSAgentNozomi,
|
||||
"noz6uoYCDijhu1V7cutCpwxNiSovEwLdRHPwmgCGDNo": enum.SWQoSAgentNozomi,
|
||||
"noz9EPNcT7WH6Sou3sr3GGjHQYVkN3DNirpbvDkv9YJ": enum.SWQoSAgentNozomi,
|
||||
"nozFrhfnNGoyqwVuwPAW4aaGqempx4PU6g6D9CJMv7Z": enum.SWQoSAgentNozomi,
|
||||
"nozNVWs5N8mgzuD3qigrCG2UoKxZttxzZ85pvAQVrbP": enum.SWQoSAgentNozomi,
|
||||
"nozUacTVWub3cL4mJmGCYjKZTnE9RbdY5AP46iQgbPJ": enum.SWQoSAgentNozomi,
|
||||
"nozWCyTPppJjRuw2fpzDhhWbW355fzosWSzrrMYB1Qk": enum.SWQoSAgentNozomi,
|
||||
"nozWNju6dY353eMkMqURqwQEoM3SFgEKC6psLCSfUne": enum.SWQoSAgentNozomi,
|
||||
"nozc5yT15LazbLTFVZzoNZCwjh3yUtW86LoUyqsBu4L": enum.SWQoSAgentNozomi,
|
||||
"nozievPk7HyK1Rqy1MPJwVQ7qQg2QoJGyP71oeDwbsu": enum.SWQoSAgentNozomi,
|
||||
"noznbgwYnBLDHu8wcQVCEw6kDrXkPdKkydGJGNXGvL7": enum.SWQoSAgentNozomi,
|
||||
"nozpEGbwx4BcGp6pvEdAh1JoC2CQGZdU6HbNP1v2p6P": enum.SWQoSAgentNozomi,
|
||||
"nozrhjhkCr3zXT3BiT4WCodYCUFeQvcdUkM7MqhKqge": enum.SWQoSAgentNozomi,
|
||||
"nozrwQtWhEdrA6W8dkbt9gnUaMs52PdAv5byipnadq3": enum.SWQoSAgentNozomi,
|
||||
"nozxNBgWohjR75vdspfxR5H9ceC7XXH99xpxhVGt3Bb": enum.SWQoSAgentNozomi,
|
||||
"EnchantKMZ93cDKwsnyvnD5WCpZLFTLVRWozFjAUzTko": enum.SWQoSAgentNozomi,
|
||||
"CJwbwPfVFZDPGKJKCtLkzDJPFrGyyroEPFjXigmJB6mr": enum.SWQoSAgentNozomi,
|
||||
"3Thhhj3omvVFfbhEHdFe8djwDZT5oS6BQ4k5KrZkYt1r": enum.SWQoSAgentNozomi,
|
||||
"6CQzBpGJn6XYcCkm77xNd944MpbjLHLsP6sCEWSZVUHS": enum.SWQoSAgentNozomi,
|
||||
"DeMZbwKtu9kteFdxL1yh6aTWqDwYfH79DKzYrgfTwAc2": enum.SWQoSAgentNozomi,
|
||||
"3Pn9ZFCsNTf9MvWbpemQccWuyHNMbBjxg1eW53ikHcpH": enum.SWQoSAgentNozomi,
|
||||
"DjfRXWegRn9bWnBvZFxAnpu1jNikcoy8iiu6ZX9AxAd5": enum.SWQoSAgentNozomi,
|
||||
"8mbjzuz8ka3zVGnry6xMEwm96tzk4yKnWgvwAT1LwEGx": enum.SWQoSAgentNozomi,
|
||||
"5KpS5Q3nUtp1cUynUxzH2bA93SWzmx2y3GwU45AeEEP5": enum.SWQoSAgentNozomi,
|
||||
"bgK8f5H4ocVdNrkUrspUFmAaEosGQtbc1JCMqLwvvRe": enum.SWQoSAgentNozomi,
|
||||
"bg7BgfutLpjFdxDNcbwQFGFkLGQT9Kww9wv6EWUHQr3": enum.SWQoSAgentNozomi,
|
||||
"bg9zUQnVkYLgAWJvL9MjP4tFDecCxbvmQRqrAuZpQUA": enum.SWQoSAgentNozomi,
|
||||
"bgu2xgHEJocs4tggHDEwNnmgduftnXfJuWoLiUYfiLW": enum.SWQoSAgentNozomi,
|
||||
"bgd4MvpBH3LaVz6sHvqFphoUex4taUe2E5mKuk4sVXn": enum.SWQoSAgentNozomi,
|
||||
"bgSfpx2Pr3bHYev6ikwTqdBo2aaPGgjEseAWhjxp6F5": enum.SWQoSAgentNozomi,
|
||||
"bgfaB4sngcm7cARjjiEvKfWE87owf2HuDfYDy8EyP45": enum.SWQoSAgentNozomi,
|
||||
"bgzmGhu9qcyLW6qR1HKQuLTY6PWktNSAuzLNmo7aiQY": enum.SWQoSAgentNozomi,
|
||||
"bgMTi1qFtbiFiHsURKW4Bfg4wjXtT8iJL7HC1z3gXsm": enum.SWQoSAgentNozomi,
|
||||
"bgDRbhSLK62ApA2PbZs1W7SecodGhTFf6udU3MWDadu": enum.SWQoSAgentNozomi,
|
||||
"bg67LJN9Ngvfq4hJbSmm7tZ2wqmn2f1pxXbXW5QfxRz": enum.SWQoSAgentNozomi,
|
||||
"bgmnrKWgN5jE8pF3PbFxRWYaho1bjCtmcTZ9VfRbhxf": enum.SWQoSAgentNozomi,
|
||||
"bgTZqxCX4ej98P1UyYJjgGmGDmst7nteSyUWDwzMxNj": enum.SWQoSAgentNozomi,
|
||||
"bgauKkwFcT8w7SHau9NufDfvmq1cy79X52bRbL6yzEB": enum.SWQoSAgentNozomi,
|
||||
"bg8WP8cEtWhdCjDd7rrwzsnz7K9f3oiEm2Qqu7TYmDn": enum.SWQoSAgentNozomi,
|
||||
"DzrVK357ynzkPtdC7jzUbXgsUY8ULUeR2ihoPcX1JB3n": enum.SWQoSAgentNozomi,
|
||||
"pfn2d1g6xkwhykkyjtoccFbC7r19ADf5dGB2YnT1Hgw": enum.SWQoSAgentNozomi,
|
||||
"pfnXi2FdpFUUn6VyoxUohNyWk2Nup3ruguTgK8jaZaF": enum.SWQoSAgentNozomi,
|
||||
"bg1rCzhyASbzib75ohpRfNY3mGJaX1k6v56WCrUkh3a": enum.SWQoSAgentNozomi,
|
||||
"axmMdWvgEnN3NFrxMfTqUURzj9NLhZL2DkHkWCdgiFV": enum.SWQoSAgentNozomi,
|
||||
"axmFmfqQwZGEUZeF3i3MqbRCDiGPfshtbdoBjk41k88": enum.SWQoSAgentNozomi,
|
||||
"bgsouue9XeHUzNwwuAKqBj1Fk1RbJkcBjvs4zkmUhLc": enum.SWQoSAgentNozomi,
|
||||
"mwGELGMgGGrNL1UibNCQeJHDE7qdPptWRYB6noUHmTj": enum.SWQoSAgentNozomi,
|
||||
"pfn9b35be4L7xh7G8P2jUzWsJAigrKDSoBeRMiyg75p": enum.SWQoSAgentNozomi,
|
||||
"pfnSbG36fCGpT8WsB1NEbQ2BH11iog6qjFqMEVCZZgV": enum.SWQoSAgentNozomi,
|
||||
"pfnaxrmMoemfvbhXek6offTNXas11GtepGQMN9UF3gk": enum.SWQoSAgentNozomi,
|
||||
"pfnKWwarhjuKKg3WV3nw3wAE8zuymigT3vuJHwZeL4s": enum.SWQoSAgentNozomi,
|
||||
"axmYVq9b1ABYqtyizMtyfJppPTPxZGXPLctB3hV6W5b": enum.SWQoSAgentNozomi,
|
||||
"pfnaZXdkjJq26auzzFKeQm7YKphuNCdDGcJVqqb6awr": enum.SWQoSAgentNozomi,
|
||||
"axmD4LFJopAcbRKCKsrrmovCZZzmKQCMEfs5qEXj8dG": enum.SWQoSAgentNozomi,
|
||||
"pfnP85qobXv2wETniKjXBhxKvgivpfT8EGAcS8sb3bq": enum.SWQoSAgentNozomi,
|
||||
"pfnSAoQWtJCDKnjmR8oduqbZYXr69Q4cFQ6VhgFkvgT": enum.SWQoSAgentNozomi,
|
||||
"pfnSbziLrSSVNqPBD9tpx3Ud4VtbxwsXjdfYv9SmBDx": enum.SWQoSAgentNozomi,
|
||||
"Vn7tfMvrvrymGYMnxhj1DV16Sz2R9YXmaXF3hiSAHuC": enum.SWQoSAgentNozomi,
|
||||
"pfnTcJ1i4mRYzbqGduF71RsooUCFkPSpk8UE7drCkjh": enum.SWQoSAgentNozomi,
|
||||
"pfnUxCuZcfP6yidkG3EsqyR5DTbyie3R74fGoA5oB3J": enum.SWQoSAgentNozomi,
|
||||
"pfnEJvqLGddJxQTA9DcYLTbVwiFdT3KmLXo6UcnmcgC": enum.SWQoSAgentNozomi,
|
||||
"pfn6dyanKiTTinHs887D7qe2S4727wzK7xi7ERGaizC": enum.SWQoSAgentNozomi,
|
||||
"bgDETv6tnt9mwYqAKebLXY5B5o6akiKJmAdU7Gd9G7H": enum.SWQoSAgentNozomi,
|
||||
"bgH7YhymSykyvMa3nAZpzvrn73owJHU5iB75S1aiLT9": enum.SWQoSAgentNozomi,
|
||||
"pfngGVVQLiVRFbLWw3Ektiv17ef9NiRZbcgdAhh4ZEW": enum.SWQoSAgentNozomi,
|
||||
"nEFs3jph8HJt7honu3k7XtGUufMnwAvSXmXcKSPxryP": enum.SWQoSAgentNozomi,
|
||||
}
|
||||
|
||||
var SWQoSFeeAddresses2Pubkeys = make(map[solana.PublicKey]string)
|
||||
|
||||
func init() {
|
||||
for k, v := range SWQoSFeeAddresses2 {
|
||||
SWQoSFeeAddresses2Pubkeys[solana.MustPublicKeyFromBase58(k)] = v
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,6 @@ const (
|
||||
SWQoSAgentBlockRazor = "blockrazor"
|
||||
SWQoSAgentAstralane = "astralane"
|
||||
SWQoSAgentStellium = "stellium"
|
||||
SWQoSAgentSoyas = "soyas"
|
||||
SWQoSAgentFast = "fast"
|
||||
)
|
||||
|
||||
@@ -11,24 +11,35 @@ import (
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
type TableInfo struct {
|
||||
overErrCount int
|
||||
|
||||
addresses []solana.PublicKey
|
||||
}
|
||||
|
||||
const MaxOverErrCount = 10
|
||||
|
||||
type AddressTables struct {
|
||||
showTableLoaded bool
|
||||
|
||||
rpcClient *rpc.Client
|
||||
mux sync.RWMutex
|
||||
loadMux sync.Mutex
|
||||
tables *lru.Cache[solana.PublicKey, []solana.PublicKey]
|
||||
tables *lru.Cache[solana.PublicKey, *TableInfo]
|
||||
loading map[solana.PublicKey]struct{}
|
||||
|
||||
pool *ants.Pool
|
||||
}
|
||||
|
||||
func NewAddressTables(rpcClient *rpc.Client) *AddressTables {
|
||||
func NewAddressTables(rpcClient *rpc.Client, showTableLoaded bool) *AddressTables {
|
||||
pool, _ := ants.NewPool(5, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
||||
cache, _ := lru.New[solana.PublicKey, []solana.PublicKey](10000)
|
||||
cache, _ := lru.New[solana.PublicKey, *TableInfo](10000)
|
||||
return &AddressTables{
|
||||
rpcClient: rpcClient,
|
||||
tables: cache,
|
||||
loading: make(map[solana.PublicKey]struct{}),
|
||||
pool: pool,
|
||||
|
||||
showTableLoaded: showTableLoaded,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +65,7 @@ func (at *AddressTables) loadAddressTable(tablePubkey solana.PublicKey) ([]solan
|
||||
return addresses, nil
|
||||
|
||||
}
|
||||
|
||||
func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uint8) []solana.PublicKey {
|
||||
at.mux.RLock()
|
||||
addresses, ok := at.tables.Get(tablePubkey)
|
||||
if !ok {
|
||||
at.mux.RUnlock()
|
||||
func (at *AddressTables) load(tablePubkey solana.PublicKey) {
|
||||
_ = at.pool.Submit(func() {
|
||||
at.loadMux.Lock()
|
||||
_, loading := at.loading[tablePubkey]
|
||||
@@ -82,25 +88,55 @@ func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uin
|
||||
delete(at.loading, tablePubkey)
|
||||
at.loadMux.Unlock()
|
||||
|
||||
at.mux.Lock()
|
||||
at.tables.Add(tablePubkey, table)
|
||||
total := at.tables.Len()
|
||||
at.mux.Unlock()
|
||||
logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total)
|
||||
at.tables.Add(tablePubkey, &TableInfo{
|
||||
addresses: table,
|
||||
})
|
||||
if at.showTableLoaded {
|
||||
total := at.tables.Len()
|
||||
logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (at *AddressTables) FillToTx(tx *VersionedTransaction, tablePubkey solana.PublicKey, idx []uint8) bool {
|
||||
addresses, ok := at.tables.Get(tablePubkey)
|
||||
if !ok {
|
||||
at.load(tablePubkey)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, i := range idx {
|
||||
if int(i) >= len(addresses.addresses) {
|
||||
logger.Error("over loadAddressTable failed", "idx", i, "table", tablePubkey)
|
||||
addresses.overErrCount++
|
||||
if addresses.overErrCount > 10 {
|
||||
at.load(tablePubkey)
|
||||
}
|
||||
return false
|
||||
}
|
||||
tx.StaticAccountKeys = append(tx.StaticAccountKeys, addresses.addresses[i])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uint8) []solana.PublicKey {
|
||||
addresses, ok := at.tables.Get(tablePubkey)
|
||||
if !ok {
|
||||
at.load(tablePubkey)
|
||||
return nil
|
||||
}
|
||||
at.mux.RUnlock()
|
||||
|
||||
var result solana.PublicKeySlice = make([]solana.PublicKey, 0, len(idx))
|
||||
for _, i := range idx {
|
||||
if int(i) >= len(addresses) {
|
||||
if int(i) >= len(addresses.addresses) {
|
||||
logger.Error("over loadAddressTable failed", "idx", i, "table", tablePubkey)
|
||||
//todo... update table?
|
||||
continue
|
||||
addresses.overErrCount++
|
||||
if addresses.overErrCount > MaxOverErrCount {
|
||||
at.load(tablePubkey)
|
||||
}
|
||||
result = append(result, addresses[i])
|
||||
break
|
||||
}
|
||||
result = append(result, addresses.addresses[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,25 +1,80 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
enableBlockStats bool
|
||||
enableParseStats bool
|
||||
|
||||
conn *grpc.ClientConn
|
||||
client ShrederServiceClient
|
||||
tableLoader *AddressTables
|
||||
subscription map[string]*SubscribeRequestFilterTransactions
|
||||
|
||||
entriesFilter map[string]FilterParams
|
||||
|
||||
parser map[solana.PublicKey]Handler
|
||||
|
||||
pool *ants.Pool
|
||||
|
||||
lastSlot uint64
|
||||
lastSlotTime time.Time
|
||||
}
|
||||
|
||||
type ClientOpts struct {
|
||||
blockStats bool
|
||||
showTableLoaded bool
|
||||
logParseStats bool
|
||||
|
||||
parser map[solana.PublicKey]Handler
|
||||
}
|
||||
|
||||
type ClientOption func(*ClientOpts)
|
||||
|
||||
func ShowTableLoaded(enable bool) ClientOption {
|
||||
return func(opts *ClientOpts) {
|
||||
opts.showTableLoaded = enable
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomParsers(parsers map[solana.PublicKey]Handler) ClientOption {
|
||||
return func(opts *ClientOpts) {
|
||||
opts.parser = parsers
|
||||
}
|
||||
}
|
||||
|
||||
func BlocksStats(enable bool) ClientOption {
|
||||
return func(opts *ClientOpts) {
|
||||
opts.blockStats = enable
|
||||
}
|
||||
}
|
||||
|
||||
// LogParsedStats enables logging of parsed transaction statistics.
|
||||
// Deprecated: do not use.
|
||||
func LogParsedStats(enable bool) ClientOption {
|
||||
return func(opts *ClientOpts) {
|
||||
opts.logParseStats = enable
|
||||
}
|
||||
}
|
||||
|
||||
func NewShrederClient(
|
||||
url string,
|
||||
rpcClient *rpc.Client,
|
||||
subscription map[string]*SubscribeRequestFilterTransactions,
|
||||
options ...ClientOption,
|
||||
) (*Client, func(), error) {
|
||||
if rpcClient == nil {
|
||||
return nil, func() {}, fmt.Errorf("rpc client is nil")
|
||||
@@ -30,11 +85,46 @@ func NewShrederClient(
|
||||
return nil, func() {}, err
|
||||
}
|
||||
|
||||
poolSize := runtime.NumCPU()*2 + 2
|
||||
logger.Info("creating shreder client", "url", url, "pool_size", poolSize)
|
||||
pool, err := ants.NewPool(poolSize, ants.WithNonblocking(false))
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
o := &ClientOpts{
|
||||
blockStats: false,
|
||||
showTableLoaded: true,
|
||||
logParseStats: false,
|
||||
parser: registered,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(o)
|
||||
}
|
||||
filterParams := make(map[string]FilterParams)
|
||||
for name, params := range subscription {
|
||||
filterParams[name] = FilterParams{
|
||||
Exclude: parseAccountArray(params.AccountExclude),
|
||||
Require: parseAccountArray(params.AccountRequired),
|
||||
Include: parseAccountArray(params.AccountInclude),
|
||||
}
|
||||
}
|
||||
if len(filterParams) == 0 {
|
||||
filterParams["default"] = FilterParams{
|
||||
Include: defaultFilterAccount,
|
||||
}
|
||||
}
|
||||
|
||||
s := &Client{
|
||||
conn: conn,
|
||||
client: NewShrederServiceClient(conn),
|
||||
subscription: subscription,
|
||||
tableLoader: NewAddressTables(rpcClient),
|
||||
entriesFilter: filterParams,
|
||||
parser: o.parser,
|
||||
tableLoader: NewAddressTables(rpcClient, o.showTableLoaded),
|
||||
pool: pool,
|
||||
|
||||
enableBlockStats: o.blockStats,
|
||||
enableParseStats: o.logParseStats,
|
||||
}
|
||||
|
||||
return s, func() {
|
||||
@@ -45,6 +135,10 @@ func NewShrederClient(
|
||||
func (c *Client) Wait() {
|
||||
logger.Debug("waiting for shreder client to stop")
|
||||
|
||||
if c.pool != nil {
|
||||
c.pool.Release()
|
||||
}
|
||||
|
||||
err := c.conn.Close()
|
||||
if err != nil {
|
||||
logger.Error("failed to close connection: ", "err", err)
|
||||
@@ -53,12 +147,56 @@ func (c *Client) Wait() {
|
||||
logger.Debug("shreder client stopped")
|
||||
}
|
||||
|
||||
func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error {
|
||||
func (c *Client) ReadEntriesSync(ctx context.Context, txCh chan<- TxSignal) error {
|
||||
stream, err := c.client.SubscribeEntries(ctx, &SubscribeEntriesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug("reading entries from shreder client")
|
||||
for {
|
||||
response, err := stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slot := response.Slot
|
||||
if c.enableBlockStats {
|
||||
now := time.Now()
|
||||
if c.lastSlotTime.IsZero() || slot > c.lastSlot {
|
||||
if !c.lastSlotTime.IsZero() {
|
||||
logger.Info("block processed", "running", c.pool.Running(), "slot", slot, "prev_slot", c.lastSlot, "delta_ms", now.Sub(c.lastSlotTime).Milliseconds())
|
||||
}
|
||||
c.lastSlot = slot
|
||||
c.lastSlotTime = now
|
||||
}
|
||||
}
|
||||
|
||||
err = c.pool.Submit(func() {
|
||||
err := entriesToVersionedTransaction(slot, bytes.NewReader(response.Entries), func(versioned VersionedTransaction) {
|
||||
// filter out vote transactions
|
||||
if FilterTransactionForEntriesWithFilter(versioned, c.entriesFilter) {
|
||||
return
|
||||
}
|
||||
go ParseTransactionWithHandler(ctx, versioned, c.tableLoader, txCh, c.parser)
|
||||
})
|
||||
if err != nil {
|
||||
logger.Debug("txparser: failed to parse entries", "error", err)
|
||||
}
|
||||
})
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug("subscribing to transactions")
|
||||
|
||||
err = stream.Send(&SubscribeTransactionsRequest{
|
||||
Transactions: c.subscription,
|
||||
})
|
||||
@@ -66,26 +204,53 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error
|
||||
return err
|
||||
}
|
||||
|
||||
// reboot the pool
|
||||
c.pool.Reboot()
|
||||
|
||||
for {
|
||||
response, err := stream.Recv()
|
||||
var response *SubscribeTransactionsResponse
|
||||
response, err = stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if c.enableBlockStats {
|
||||
slot := response.Transaction.Slot
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// txData := response.Transaction
|
||||
|
||||
err := c.pool.Submit(func() {
|
||||
versioned, err := toVersionedTransaction(response.Transaction)
|
||||
if err != nil {
|
||||
logger.Debug("txparser: failed to convert to versioned transaction", "error", err)
|
||||
return
|
||||
}
|
||||
ParseTransactionWithHandler(ctx, versioned, c.tableLoader, txCh, c.parser)
|
||||
})
|
||||
if err != nil && errors.Is(err, ants.ErrPoolOverload) {
|
||||
logger.Warn("task pool is full")
|
||||
}
|
||||
}
|
||||
|
||||
// sync waiting for all tasks to complete
|
||||
c.pool.Release()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
txBatch := ParseTransaction(response.Transaction, c.tableLoader)
|
||||
if len(txBatch) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// set fixed source for tx signals
|
||||
for _, tx := range txBatch {
|
||||
tx.Source = "shreder"
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case txCh <- txBatch:
|
||||
}
|
||||
func parseAccountArray(accountArray []string) []solana.PublicKey {
|
||||
var result []solana.PublicKey
|
||||
for _, acc := range accountArray {
|
||||
result = append(result, solana.MustPublicKeyFromBase58(acc))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
1989
pkg/shreder/dflow_idl.json
Normal file
1989
pkg/shreder/dflow_idl.json
Normal file
File diff suppressed because it is too large
Load Diff
8471
pkg/shreder/dlmm_idl.json
Normal file
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
272
pkg/shreder/entry.go
Normal 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
30
pkg/shreder/entry_test.go
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,88 +0,0 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeRouteV2Arg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 0",
|
||||
hexData: "bb64facc31c4af14809fd500000000002222e8db1800000064000a000000020000005601fe102700016310270102",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 1",
|
||||
hexData: "bb64facc31c4af144ff91634b90000004e6c4d05000000002c013200000003000000520000000000000000102700014f102701024310270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 2",
|
||||
hexData: "bb64facc31c4af14ba2eafa02c1d0000777a9b2200000000f4010a0000000100000052000000000000000010270001",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 3",
|
||||
hexData: "bb64facc31c4af144a3521186b07000030508d0e00000000c201320000000300000052000000000000000010270001740110270102590010270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 4",
|
||||
hexData: "bb64facc31c4af14092d05050000000013701f198c0100008102380100000300000059011027000168001027010251000000000000000010270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 5",
|
||||
hexData: "bb64facc31c4af1480969800000000006f44ad39bd0000001202320000000200000068001027000151000000000000000010270102",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("raw bytes: %x", instrData[8:])
|
||||
args, err := decodeJupiterV6RouteV2Arg(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode jupiter arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeRouteArg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 0",
|
||||
hexData: "e517cb977ae3ad2a030000004f6400014f64010251000000000000000064020340420f00000000005c1c81900e000000640000",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 1",
|
||||
hexData: "e517cb977ae3ad2a0200000028640001510000000000000000640102c09ee605000000005e1bc48efa000000d00700",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("raw bytes: %x", instrData[8:])
|
||||
args, err := decodeJupiterV6RouteArg(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode jupiter arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package shreder
|
||||
|
||||
//func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
|
||||
//
|
||||
//}
|
||||
129
pkg/shreder/program_azcz.go
Normal file
129
pkg/shreder/program_azcz.go
Normal 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
|
||||
}
|
||||
118
pkg/shreder/program_binancewallet.go
Normal file
118
pkg/shreder/program_binancewallet.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
)
|
||||
|
||||
var binanceWalletProgramID = solana.MustPublicKeyFromBase58("B3111yJCeHBcA1bizdJjUFPALfhAfSRnAbJzGUtnt56A")
|
||||
|
||||
const (
|
||||
binanceWalletMinDataLen = 72
|
||||
binanceWalletSolOffset = 23
|
||||
binanceWalletTokenOff = 39
|
||||
binanceWalletSolRepeat = 51
|
||||
binanceWalletSideOff = 71
|
||||
|
||||
binanceWalletPumpBuy = 0x05
|
||||
binanceWalletPumpSell = 0x06
|
||||
)
|
||||
|
||||
var binanceWalletMarker = []byte{0x13, 0x2c, 0x82, 0x94, 0x48, 0x38, 0x2c, 0xee}
|
||||
|
||||
func parseBinanceWalletInstruction(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) < len(binanceWalletMarker) || !bytes.Contains(instruction.Data, binanceWalletMarker) {
|
||||
return nil, nil
|
||||
}
|
||||
if len(instruction.Data) < binanceWalletMinDataLen {
|
||||
return nil, fmt.Errorf("data too short for binance wallet, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
side := instruction.Data[binanceWalletSideOff]
|
||||
if side != binanceWalletPumpBuy && side != binanceWalletPumpSell {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(instruction.Accounts) <= 8 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
wsolIdx := 7
|
||||
tokenIdx := 8
|
||||
if side == binanceWalletPumpSell {
|
||||
wsolIdx = 8
|
||||
tokenIdx = 7
|
||||
}
|
||||
|
||||
wsolKey, err := tx.GetAccount(int(instruction.Accounts[wsolIdx]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !wsolKey.Equals(solana.WrappedSol) {
|
||||
return nil, nil
|
||||
}
|
||||
mint, err := tx.GetAccount(int(instruction.Accounts[tokenIdx]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
amountA := binary.LittleEndian.Uint64(instruction.Data[binanceWalletSolOffset : binanceWalletSolOffset+8])
|
||||
if amountA == 0 && len(instruction.Data) >= binanceWalletSolRepeat+8 {
|
||||
repeat := binary.LittleEndian.Uint64(instruction.Data[binanceWalletSolRepeat : binanceWalletSolRepeat+8])
|
||||
if repeat > 0 {
|
||||
amountA = repeat
|
||||
}
|
||||
}
|
||||
amountB := binary.LittleEndian.Uint64(instruction.Data[binanceWalletTokenOff : binanceWalletTokenOff+8])
|
||||
|
||||
solAmount := amountA
|
||||
tokenAmount := amountB
|
||||
if side == binanceWalletPumpSell {
|
||||
solAmount = amountB
|
||||
tokenAmount = amountA
|
||||
}
|
||||
|
||||
maker := ""
|
||||
if len(tx.StaticAccountKeys) > 0 {
|
||||
maker = tx.StaticAccountKeys[0].String()
|
||||
} else if len(instruction.Accounts) > 0 {
|
||||
key, err := tx.GetAccount(int(instruction.Accounts[0]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maker = key.String()
|
||||
}
|
||||
|
||||
event := "buy"
|
||||
exactIn := true
|
||||
if side == binanceWalletPumpSell {
|
||||
event = "sell"
|
||||
exactIn = false
|
||||
}
|
||||
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "binancewallet",
|
||||
Maker: maker,
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(tokenAmount),
|
||||
Token1Amount: formatSolAmount(solAmount),
|
||||
Program: "Pump",
|
||||
Event: event,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: exactIn,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: tokenAmount,
|
||||
Token1AmountUint64: solAmount,
|
||||
}}, nil
|
||||
}
|
||||
197
pkg/shreder/program_bloomrouter.go
Normal file
197
pkg/shreder/program_bloomrouter.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
)
|
||||
|
||||
var bloomRouterProgramID = solana.MustPublicKeyFromBase58("b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1")
|
||||
var pumpFunAccount = solana.MustPublicKeyFromBase58("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
|
||||
|
||||
var bloomRouterSwapDiscriminator = []byte{0xf1, 0x57, 0x27, 0x38, 0x01, 0x4d, 0x0e, 0x63}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if !bytes.Equal(instruction.Data[:8], bloomRouterSwapDiscriminator) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
findPumpFun := func() (solana.PublicKey, solana.PublicKey, error) {
|
||||
var mint solana.PublicKey
|
||||
foundPumpFun := false
|
||||
for i, acctIdx := range instruction.Accounts {
|
||||
key, err := tx.GetAccount(int(acctIdx))
|
||||
if err != nil {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, err
|
||||
}
|
||||
if key.Equals(pumpFunAccount) {
|
||||
if i+2 >= len(instruction.Accounts) {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short for pumpfun mint, idx=%d len=%d", i, len(instruction.Accounts))
|
||||
}
|
||||
mintKey, err := tx.GetAccount(int(instruction.Accounts[i+2]))
|
||||
if err != nil {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, err
|
||||
}
|
||||
mint = mintKey
|
||||
foundPumpFun = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundPumpFun {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, nil
|
||||
}
|
||||
return mint, wrappedSOL, nil
|
||||
}
|
||||
|
||||
findRaydiumLaunchLab := func(isBuy bool) (solana.PublicKey, solana.PublicKey, error) {
|
||||
offset := 0
|
||||
if isBuy {
|
||||
offset = 10
|
||||
} else {
|
||||
offset = 9
|
||||
}
|
||||
var base solana.PublicKey
|
||||
var quote solana.PublicKey
|
||||
foundRaydiumLaunchLab := false
|
||||
for i, acctIdx := range instruction.Accounts {
|
||||
key, err := tx.GetAccount(int(acctIdx))
|
||||
if err != nil {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, err
|
||||
}
|
||||
if key.Equals(raydiumLaunchLabProgramID) {
|
||||
if i+offset+1 >= len(instruction.Accounts) {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, fmt.Errorf("accounts too short for raydium launch lab mint, idx=%d len=%d", i, len(instruction.Accounts))
|
||||
}
|
||||
var err error
|
||||
base, err = tx.GetAccount(int(instruction.Accounts[i+offset]))
|
||||
if err != nil {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, err
|
||||
}
|
||||
quote, err = tx.GetAccount(int(instruction.Accounts[i+offset+1]))
|
||||
if err != nil {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, err
|
||||
}
|
||||
foundRaydiumLaunchLab = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundRaydiumLaunchLab {
|
||||
return solana.PublicKey{}, solana.PublicKey{}, nil
|
||||
}
|
||||
return base, quote, nil
|
||||
}
|
||||
|
||||
var (
|
||||
amount uint64
|
||||
sol uint64
|
||||
exactIn bool
|
||||
event string
|
||||
program string
|
||||
base solana.PublicKey
|
||||
quote solana.PublicKey
|
||||
err error
|
||||
)
|
||||
|
||||
args, err := decodeBloomRouterArgs(instruction.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch args.Side {
|
||||
case 0:
|
||||
event = "buy"
|
||||
exactIn = true
|
||||
program = "Pump"
|
||||
base, quote, err = findPumpFun()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 1:
|
||||
event = "sell"
|
||||
program = "Pump"
|
||||
base, quote, err = findPumpFun()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 0x0b00:
|
||||
event = "buy"
|
||||
exactIn = true
|
||||
program = "RaydiumLaunchLab"
|
||||
base, quote, err = findRaydiumLaunchLab(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !quote.Equals(wrappedSOL) {
|
||||
return nil, nil
|
||||
}
|
||||
case 0x0b01:
|
||||
event = "sell"
|
||||
program = "RaydiumLaunchLab"
|
||||
base, quote, err = findRaydiumLaunchLab(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !quote.Equals(wrappedSOL) {
|
||||
return nil, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "bloomrouter",
|
||||
Maker: maker.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: quote.String(),
|
||||
Token0Amount: formatTokenAmount(amount),
|
||||
Token1Amount: formatSolAmount(sol),
|
||||
Program: program,
|
||||
Event: event,
|
||||
ExactSOL: exactIn,
|
||||
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
|
||||
}
|
||||
77
pkg/shreder/program_bobo.go
Normal file
77
pkg/shreder/program_bobo.go
Normal 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
111
pkg/shreder/program_bonk.go
Normal 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
|
||||
}
|
||||
}
|
||||
134
pkg/shreder/program_dbot.go
Normal file
134
pkg/shreder/program_dbot.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var dbotProgramID = solana.MustPublicKeyFromBase58("DBotWvSso9oD1ZB3aHx2LiD2ZoFpF8PbKjaT4uHKLLVs")
|
||||
|
||||
var (
|
||||
dbotPumpFunBuyIX = []byte{0x4e, 0x13, 0x6d, 0x72, 0x3d, 0x72, 0xbe, 0x9d}
|
||||
dbotPumpAmmBuyIX = []byte{0x99, 0x76, 0xb6, 0x1e, 0xe4, 0x03, 0xdc, 0xf4}
|
||||
)
|
||||
|
||||
func parseDbotInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
|
||||
if instructionIndex >= len(tx.Instructions) {
|
||||
return nil, fmt.Errorf("instruction index out of bounds")
|
||||
}
|
||||
ix := tx.Instructions[instructionIndex]
|
||||
if len(ix.Data) < 8 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
isPumpFun := false
|
||||
isPumpAmm := false
|
||||
|
||||
if len(ix.Accounts) > 11 {
|
||||
key, err := tx.GetAccount(int(ix.Accounts[11]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.Equals(pumpProgramID) {
|
||||
isPumpFun = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(ix.Accounts) > 16 {
|
||||
key, err := tx.GetAccount(int(ix.Accounts[16]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.Equals(pumpAmmProgramID) {
|
||||
isPumpAmm = true
|
||||
}
|
||||
}
|
||||
|
||||
disc := ix.Data[:8]
|
||||
|
||||
if isPumpFun {
|
||||
if !bytes.Equal(disc, dbotPumpFunBuyIX) {
|
||||
return nil, nil
|
||||
}
|
||||
if len(ix.Data) < 16 {
|
||||
return nil, fmt.Errorf("data too short for dbot pumpfun buy args, len=%d", len(ix.Data))
|
||||
}
|
||||
if len(ix.Accounts) < 3 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
mint, err := tx.GetAccount(int(ix.Accounts[2]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
solAmount := binary.LittleEndian.Uint64(ix.Data[8:16])
|
||||
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "dbot",
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: decimal.Zero,
|
||||
Token1Amount: formatSolAmount(solAmount),
|
||||
Program: "Pump",
|
||||
Event: "buy",
|
||||
ExactSOL: true,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: 0,
|
||||
Token1AmountUint64: solAmount,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
if isPumpAmm {
|
||||
if !bytes.Equal(disc, dbotPumpAmmBuyIX) {
|
||||
return nil, nil
|
||||
}
|
||||
if len(ix.Data) < 16 {
|
||||
return nil, fmt.Errorf("data too short for dbot pumpamm buy args, len=%d", len(ix.Data))
|
||||
}
|
||||
if len(ix.Accounts) < 5 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
base, err := tx.GetAccount(int(ix.Accounts[3]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quote, err := tx.GetAccount(int(ix.Accounts[4]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !quote.Equals(solana.WrappedSol) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
solAmount := binary.LittleEndian.Uint64(ix.Data[8:16])
|
||||
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "dbot",
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: decimal.Zero,
|
||||
Token1Amount: formatSolAmount(solAmount),
|
||||
Program: "PumpAMM",
|
||||
Event: "buy",
|
||||
ExactSOL: true,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: 0,
|
||||
Token1AmountUint64: solAmount,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
426
pkg/shreder/program_dflow.go
Normal file
426
pkg/shreder/program_dflow.go
Normal file
@@ -0,0 +1,426 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
bin "github.com/gagliardetto/binary"
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var (
|
||||
dflowProgramID = solana.MustPublicKeyFromBase58("DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH")
|
||||
dflowProgramString = dflowProgramID.String()
|
||||
|
||||
dflowSwapDisc = []byte{248, 198, 158, 145, 225, 117, 135, 200}
|
||||
dflowSwap2Disc = []byte{65, 75, 63, 76, 235, 91, 91, 136}
|
||||
dflowSwapWithDestinationDisc = []byte{168, 172, 24, 77, 197, 156, 135, 101}
|
||||
dflowSwapWithDestinationNative = []byte{205, 77, 127, 108, 241, 32, 196, 195}
|
||||
dflowSwap2WithDestinationDisc = []byte{95, 123, 213, 246, 122, 1, 86, 231}
|
||||
dflowSwap2WithDestinationNative = []byte{222, 100, 184, 146, 186, 196, 105, 165}
|
||||
|
||||
wrappedSOL = solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112")
|
||||
)
|
||||
|
||||
// Action enum tags (0-based, per dflow_idl Action variants)
|
||||
const (
|
||||
ActWhirlpoolsSwap uint8 = iota
|
||||
ActClearpoolsSwap
|
||||
ActRaydiumAmmSwap
|
||||
ActLifinityV2Swap
|
||||
ActMeteoraDlmmSwap
|
||||
ActRaydiumClmmSwap
|
||||
ActRaydiumClmmSwapV2
|
||||
ActPhoenixSwap
|
||||
ActPumpFunBuy
|
||||
ActPumpFunSell
|
||||
ActGammaSwap
|
||||
ActObricV2Swap
|
||||
ActPumpFunAmmBuy
|
||||
ActPumpFunAmmSell
|
||||
ActSolFiSwap
|
||||
ActRubiconSwap
|
||||
ActMeteoraDammV1Swap
|
||||
ActRaydiumCpSwap
|
||||
ActStabbleStableSwap
|
||||
ActTesseraVSwap
|
||||
ActMeteoraDammV2Swap
|
||||
ActRaydiumLaunchlabSwap
|
||||
ActMeteoraDbcSwap
|
||||
ActHumidiFiSwap
|
||||
ActWhirlpoolsSwapV2
|
||||
ActMeteoraDlmmSwapV2
|
||||
ActZeroFiSwap
|
||||
ActAlphaQSwap
|
||||
ActTokenSwap
|
||||
ActSolFiV2Swap
|
||||
ActMozartSwap
|
||||
ActDFlowDynamicRouteV1
|
||||
ActHeavenSwap
|
||||
ActNexusSwap
|
||||
ActSarosDlmmSwap
|
||||
ActTransferFee
|
||||
ActTransferFeeWithMint
|
||||
ActRecordId
|
||||
ActRecordId2
|
||||
ActManifestSwap
|
||||
ActBisonFiSwap
|
||||
ActSanctumInfinitySwap
|
||||
ActSanctumInfinityLiquidity
|
||||
ActOpenPredictionsOrder
|
||||
ActScorchSwap
|
||||
ActIncludeAccount
|
||||
|
||||
ActDFLOWStabbleWeightedSwap
|
||||
ActVertigoSwap
|
||||
ActSetMinimumLegOutputs
|
||||
ActSetMinimumLegPrices
|
||||
)
|
||||
|
||||
// DynamicRouteV1CandidateAction tags
|
||||
const (
|
||||
drv1SolFi uint8 = iota
|
||||
drv1Rubicon
|
||||
drv1TesseraV
|
||||
drv1HumidiFi
|
||||
drv1SolFiV2
|
||||
drv1Mozart
|
||||
drv1ObricV2
|
||||
drv1Nexus
|
||||
)
|
||||
|
||||
// PumpFun*Options { amount: u64, orchestrator_flags: OrchestratorFlags{flags u8} }
|
||||
type pumpFunAction struct {
|
||||
Amount uint64
|
||||
Flags uint8
|
||||
}
|
||||
|
||||
type dflowAction struct {
|
||||
Tag uint8
|
||||
Pump *pumpFunAction
|
||||
}
|
||||
|
||||
type dflowSwapParams struct {
|
||||
Actions []dflowAction
|
||||
}
|
||||
|
||||
// bytes to skip for Action variants; only PumpFun* actions are decoded.
|
||||
func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
|
||||
switch tag {
|
||||
case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2, ActDFLOWStabbleWeightedSwap, ActVertigoSwap:
|
||||
// amount u64 + bool + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 1)
|
||||
case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap,
|
||||
ActSolFiSwap, ActRubiconSwap, ActMeteoraDammV1Swap, ActRaydiumCpSwap,
|
||||
ActStabbleStableSwap, ActTesseraVSwap, ActMeteoraDammV2Swap, ActRaydiumLaunchlabSwap,
|
||||
ActZeroFiSwap, ActAlphaQSwap, ActTokenSwap, ActSolFiV2Swap, ActMozartSwap, ActHeavenSwap,
|
||||
ActNexusSwap, ActSarosDlmmSwap, ActManifestSwap, ActBisonFiSwap:
|
||||
// amount u64 + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1)
|
||||
case ActMeteoraDlmmSwap, ActRaydiumClmmSwap, ActRaydiumClmmSwapV2, ActMeteoraDlmmSwapV2:
|
||||
// amount u64 + u8 + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 1)
|
||||
case ActPhoenixSwap:
|
||||
// amount u64 + side u8 + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 1)
|
||||
case ActGammaSwap:
|
||||
// amount u64 + endorsed bool + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 1)
|
||||
case ActPumpFunAmmSell, ActPumpFunAmmBuy, ActPumpFunBuy, ActPumpFunSell:
|
||||
amt, err := dec.ReadUint64(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flg, err := dec.ReadUint8()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pumpFunAction{Amount: amt, Flags: flg}, nil
|
||||
case ActMeteoraDbcSwap:
|
||||
// amount u64 + is_rate_limiter_applied bool + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 1)
|
||||
case ActHumidiFiSwap:
|
||||
// amount u64 + swap_id u64 + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 8 + 1)
|
||||
case ActDFlowDynamicRouteV1:
|
||||
// candidate_actions Vec<DynamicRouteV1CandidateAction> + amount u64 + orchestrator_flags u8
|
||||
ln, err := dec.ReadUint32(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for j := uint32(0); j < ln; j++ {
|
||||
t, err := dec.ReadUint8()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t == drv1HumidiFi {
|
||||
if err := dec.SkipBytes(8); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// other variants carry no payload
|
||||
}
|
||||
if err := dec.SkipBytes(8); err != nil { // amount
|
||||
return nil, err
|
||||
}
|
||||
return nil, dec.SkipBytes(1) // orchestrator_flags
|
||||
case ActTransferFee, ActTransferFeeWithMint:
|
||||
return nil, dec.SkipBytes(8)
|
||||
case ActRecordId:
|
||||
return nil, dec.SkipBytes(76)
|
||||
case ActRecordId2:
|
||||
return nil, dec.SkipBytes(4)
|
||||
case ActSanctumInfinitySwap:
|
||||
// amount u64 + src_lst_value_calc_accs u8 + dst_lst_value_calc_accs u8 + src_lst_index u32 + dst_lst_index u32 + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 1 + 4 + 4 + 1)
|
||||
case ActSanctumInfinityLiquidity:
|
||||
// amount u64 + lst_value_calc_accs u8 + lst_index u32 + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 4 + 1)
|
||||
case ActOpenPredictionsOrder:
|
||||
// nonce u64 + order_outcome u8 + quoted_out_amount u64 + slippage_bps u16 + platform_fee_recipient_vault pubkey(32) + platform_fee_scale u16
|
||||
return nil, dec.SkipBytes(8 + 1 + 8 + 2 + 32 + 2)
|
||||
case ActScorchSwap:
|
||||
// amount u64 + id u128 + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 16 + 1)
|
||||
case ActIncludeAccount:
|
||||
return nil, nil
|
||||
case ActSetMinimumLegOutputs:
|
||||
ln, err := dec.ReadUint32(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, dec.SkipBytes(uint(8 * ln))
|
||||
case ActSetMinimumLegPrices:
|
||||
// Vec<(u64, u8)>; read length and skip the pairs
|
||||
ln, err := dec.ReadUint32(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, dec.SkipBytes(uint(uint64(ln) * (8 + 1)))
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported action tag %d", tag)
|
||||
}
|
||||
}
|
||||
|
||||
// SwapParams: actions Vec<Action>, quoted_out_amount u64, slippage_bps u16, platform_fee_bps u16
|
||||
func decodeSwapParams(data []byte) (*dflowSwapParams, error) {
|
||||
dec := bin.NewBorshDecoder(data)
|
||||
out := &dflowSwapParams{}
|
||||
ln, err := dec.ReadUint32(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Actions = make([]dflowAction, 0, ln)
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
tag, err := dec.ReadUint8()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("actions[%d] tag: %w", i, err)
|
||||
}
|
||||
pump, err := skipDflowAction(dec, tag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("actions[%d]: %w", i, err)
|
||||
}
|
||||
out.Actions = append(out.Actions, dflowAction{Tag: tag, Pump: pump})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Swap2Params: actions Vec<Action>, quoted_out_amount u64, slippage_bps u16, platform_fee_bps u16, positive_slippage_fee_limit_pct u8
|
||||
func decodeSwap2Params(data []byte) (*dflowSwapParams, error) {
|
||||
dec := bin.NewBorshDecoder(data)
|
||||
out := &dflowSwapParams{}
|
||||
ln, err := dec.ReadUint32(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Actions = make([]dflowAction, 0, ln)
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
tag, err := dec.ReadUint8()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("actions[%d] tag: %w", i, err)
|
||||
}
|
||||
pump, err := skipDflowAction(dec, tag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("actions[%d]: %w", i, err)
|
||||
}
|
||||
out.Actions = append(out.Actions, dflowAction{Tag: tag, Pump: pump})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func findDflowPumpAmmMints(tx VersionedTransaction, accounts []uint8) (solana.PublicKey, solana.PublicKey, bool, error) {
|
||||
for i, acctIdx := range accounts {
|
||||
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")
|
||||
}
|
||||
ix := tx.Instructions[instructionIndex]
|
||||
if len(ix.Data) < 8 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
disc := ix.Data[:8]
|
||||
payload := ix.Data[8:]
|
||||
|
||||
var params *dflowSwapParams
|
||||
switch {
|
||||
case bytes.Equal(disc, dflowSwapDisc), bytes.Equal(disc, dflowSwapWithDestinationDisc), bytes.Equal(disc, dflowSwapWithDestinationNative):
|
||||
params, err = decodeSwapParams(payload)
|
||||
case bytes.Equal(disc, dflowSwap2Disc), bytes.Equal(disc, dflowSwap2WithDestinationDisc), bytes.Equal(disc, dflowSwap2WithDestinationNative):
|
||||
params, err = decodeSwap2Params(payload)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if params == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
pumpAmmBuy *pumpFunAction
|
||||
pumpAmmSell *pumpFunAction
|
||||
pumpBuy *pumpFunAction
|
||||
pumpSell *pumpFunAction
|
||||
)
|
||||
for _, act := range params.Actions {
|
||||
if act.Pump == nil {
|
||||
continue
|
||||
}
|
||||
switch act.Tag {
|
||||
case ActPumpFunAmmSell:
|
||||
pumpAmmSell = act.Pump
|
||||
case ActPumpFunAmmBuy:
|
||||
pumpAmmBuy = act.Pump
|
||||
case ActPumpFunBuy:
|
||||
pumpBuy = act.Pump
|
||||
case ActPumpFunSell:
|
||||
pumpSell = act.Pump
|
||||
}
|
||||
}
|
||||
|
||||
out := make(TxSignalBatch, 0, 2)
|
||||
if pumpAmmSell != nil || pumpAmmBuy != nil {
|
||||
event := "sell"
|
||||
amt := pumpAmmSell
|
||||
isBuy := false
|
||||
if amt == nil {
|
||||
event = "buy"
|
||||
isBuy = true
|
||||
amt = pumpAmmBuy
|
||||
}
|
||||
baseMint, quoteMint, ok, err := findDflowPumpAmmMints(tx, ix.Accounts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok && quoteMint.Equals(solana.WrappedSol) {
|
||||
var (
|
||||
token0Amount decimal.Decimal
|
||||
token1Amount decimal.Decimal
|
||||
token0AmountUint64 uint64
|
||||
token1AmountUint64 uint64
|
||||
exactSol bool
|
||||
)
|
||||
if isBuy {
|
||||
exactSol = true
|
||||
token1Amount = formatSolAmount(amt.Amount)
|
||||
token1AmountUint64 = amt.Amount
|
||||
} else {
|
||||
token0Amount = formatTokenAmount(amt.Amount)
|
||||
token0AmountUint64 = amt.Amount
|
||||
}
|
||||
out = append(out, &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Program: "PumpAMM",
|
||||
Event: event,
|
||||
Token0Address: baseMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: token0Amount,
|
||||
Token1Amount: token1Amount,
|
||||
ExactSOL: exactSol,
|
||||
Token0AmountUint64: token0AmountUint64,
|
||||
Token1AmountUint64: token1AmountUint64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if pumpSell != nil || pumpBuy != nil {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
61
pkg/shreder/program_dflow_test.go
Normal file
61
pkg/shreder/program_dflow_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDFlowDecodedSwapParams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "DFlow swap Test 0",
|
||||
hexData: "f8c69e91e17587c806000000256cb411cd4dcea8c073833936254cc3a7a6f1bc3e1106af1fceaed1bf6d75184d8149476a66d1f0d4c23c177e81d73b8b11297c7f7d8a8d6e339939647915d8096cfcdd170000000093000000300300000000000000000000000000000000000000a84325c4000000002d0decfc36e0bc09000001197bcde00df80000000180130edffead0800000081711ebdc4000000002c013200",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
//t.Logf("raw bytes: %x", instrData[8:])
|
||||
args, err := decodeSwapParams(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode dflow arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDFlowV2DecodedSwapParams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "DFlow swap 2 Test 0",
|
||||
hexData: "414b3f4ceb5b5b880300000025427ee16eed91684faaad4a3f161acd31d92bbc3d1ba0e2ebdb4678448fd5a7aeade9c8b38e8755e811f3373a0056cd5647e4cc3510135f98e97cb03c046ade049d08de17000000007800000023f50a0000000000002e1bfe04000000000001c3e13700000000003601000005",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
args, err := decodeSwap2Params(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode dflow arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
}
|
||||
232
pkg/shreder/program_dlmm.go
Normal file
232
pkg/shreder/program_dlmm.go
Normal file
@@ -0,0 +1,232 @@
|
||||
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
|
||||
}
|
||||
lbPair, err := tx.GetAccount(int(instruction.Accounts[0]))
|
||||
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,
|
||||
LbPairAddress: lbPair.String(),
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: token0AmountUint64,
|
||||
Token1AmountUint64: token1AmountUint64,
|
||||
}}, nil
|
||||
}
|
||||
72
pkg/shreder/program_f5tf.go
Normal file
72
pkg/shreder/program_f5tf.go
Normal 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
|
||||
}
|
||||
71
pkg/shreder/program_fjsz.go
Normal file
71
pkg/shreder/program_fjsz.go
Normal 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
|
||||
}
|
||||
350
pkg/shreder/program_flas.go
Normal file
350
pkg/shreder/program_flas.go
Normal file
@@ -0,0 +1,350 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
|
||||
var (
|
||||
flasBuyTokensIXs = [][]byte{
|
||||
{0x00, 0x01, 0x21},
|
||||
{0x00, 0x01, 0x1b},
|
||||
}
|
||||
flasSellTokensIXs = [][]byte{
|
||||
{0x01, 0x01, 0x1a},
|
||||
}
|
||||
flasAmmBuyTokensIXs = [][]byte{
|
||||
{0x00, 0x02, 0x1f},
|
||||
{0x00, 0x02, 0x02},
|
||||
}
|
||||
flasAmmSellTokensIXs = [][]byte{
|
||||
{0x01, 0x02, 0x1f},
|
||||
{0x01, 0x02, 0x02},
|
||||
}
|
||||
flasBonkBuyTokensIXs = [][]byte{
|
||||
{0x00, 0x02, 0x07},
|
||||
}
|
||||
flasBonkSellTokensIXs = [][]byte{
|
||||
{0x01, 0x02, 0x07},
|
||||
}
|
||||
)
|
||||
|
||||
type flasArgs struct {
|
||||
Amount1 uint64
|
||||
Amount2 uint64
|
||||
Placeholder [3]uint8
|
||||
}
|
||||
|
||||
func decodeFlasArgs(data []byte) (flasArgs, error) {
|
||||
if len(data) < 20 {
|
||||
return flasArgs{}, fmt.Errorf("data too short for args flas instruction, len: %d", len(data))
|
||||
}
|
||||
return flasArgs{
|
||||
Amount1: binary.LittleEndian.Uint64(data[1:9]),
|
||||
Amount2: binary.LittleEndian.Uint64(data[9:17]),
|
||||
Placeholder: [3]uint8{data[17], data[18], data[19]},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func matchFlasMethod(data []byte, methods [][]byte) bool {
|
||||
for _, method := range methods {
|
||||
if matchMethod(data, method) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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]
|
||||
var (
|
||||
err error
|
||||
txSignal *TxSignal
|
||||
)
|
||||
if matchFlasMethod(methodData, flasBuyTokensIXs) {
|
||||
txSignal, err = parseFlasBuy(tx, instructionIndex)
|
||||
} else if matchFlasMethod(methodData, flasSellTokensIXs) {
|
||||
txSignal, err = parseFlasSell(tx, instructionIndex)
|
||||
} else if matchFlasMethod(methodData, flasAmmBuyTokensIXs) {
|
||||
txSignal, err = parseFlasAmmBuy(tx, instructionIndex)
|
||||
} else if matchFlasMethod(methodData, flasAmmSellTokensIXs) {
|
||||
txSignal, err = parseFlasAmmSell(tx, instructionIndex)
|
||||
} else if matchFlasMethod(methodData, flasBonkBuyTokensIXs) {
|
||||
txSignal, err = parseFlasBonkBuy(tx, instructionIndex)
|
||||
} else if matchFlasMethod(methodData, flasBonkSellTokensIXs) {
|
||||
txSignal, err = parseFlasBonkSell(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
|
||||
}
|
||||
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
||||
}
|
||||
token0Amount := formatTokenAmount(args.Amount1)
|
||||
token0AmountUint64 := args.Amount1
|
||||
if len(instruction.Accounts) == 52 {
|
||||
token0Amount = decimal.Zero
|
||||
token0AmountUint64 = 0
|
||||
}
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: token0Amount,
|
||||
Token1Amount: formatSolAmount(args.Amount2),
|
||||
Program: "PumpAMM",
|
||||
Event: "sell",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: token0AmountUint64,
|
||||
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
|
||||
}
|
||||
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if 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) < 11 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if 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) < 11 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if 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
|
||||
}
|
||||
|
||||
func parseFlasBonkBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
||||
instruction := tx.Instructions[instructionIndex]
|
||||
if len(instruction.Accounts) < 17 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
base, err := tx.GetAccount(int(instruction.Accounts[15]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quote, err := tx.GetAccount(int(instruction.Accounts[16]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !quote.Equals(wrappedSOL) {
|
||||
// just ignore this
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if 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: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(args.Amount2),
|
||||
Token1Amount: formatSolAmount(args.Amount1),
|
||||
Program: "RaydiumLaunchLab",
|
||||
Event: "buy",
|
||||
ExactSOL: true,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: args.Amount2,
|
||||
Token1AmountUint64: args.Amount1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseFlasBonkSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
||||
instruction := tx.Instructions[instructionIndex]
|
||||
if len(instruction.Accounts) < 17 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
base, err := tx.GetAccount(int(instruction.Accounts[15]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quote, err := tx.GetAccount(int(instruction.Accounts[16]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !quote.Equals(wrappedSOL) {
|
||||
// just ignore this
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if 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: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(args.Amount1),
|
||||
Token1Amount: formatSolAmount(args.Amount2),
|
||||
Program: "RaydiumLaunchLab",
|
||||
Event: "sell",
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: args.Amount1,
|
||||
Token1AmountUint64: args.Amount2,
|
||||
}, nil
|
||||
}
|
||||
78
pkg/shreder/program_gmgn.go
Normal file
78
pkg/shreder/program_gmgn.go
Normal 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
|
||||
}
|
||||
1911
pkg/shreder/program_juptierv6.go
Normal file
1911
pkg/shreder/program_juptierv6.go
Normal file
File diff suppressed because it is too large
Load Diff
161
pkg/shreder/program_juptierv6_test.go
Normal file
161
pkg/shreder/program_juptierv6_test.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
)
|
||||
|
||||
func TestDecodeRouteV2Arg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 0",
|
||||
hexData: "bb64facc31c4af14809fd500000000002222e8db1800000064000a000000020000005601fe102700016310270102",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 1",
|
||||
hexData: "bb64facc31c4af144ff91634b90000004e6c4d05000000002c013200000003000000520000000000000000102700014f102701024310270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 2",
|
||||
hexData: "bb64facc31c4af14ba2eafa02c1d0000777a9b2200000000f4010a0000000100000052000000000000000010270001",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 3",
|
||||
hexData: "bb64facc31c4af144a3521186b07000030508d0e00000000c201320000000300000052000000000000000010270001740110270102590010270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 4",
|
||||
hexData: "bb64facc31c4af14092d05050000000013701f198c0100008102380100000300000059011027000168001027010251000000000000000010270203",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteV2Arg Test 5",
|
||||
hexData: "bb64facc31c4af1480969800000000006f44ad39bd0000001202320000000200000068001027000151000000000000000010270102",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("raw bytes: %x", instrData[8:])
|
||||
args, err := decodeJupiterV6RouteV2Arg(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode jupiter arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeRouteArg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hexData string
|
||||
}{
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 0",
|
||||
hexData: "e517cb977ae3ad2a030000004f6400014f64010251000000000000000064020340420f00000000005c1c81900e000000640000",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 1",
|
||||
hexData: "e517cb977ae3ad2a03000000646400017ab0b6c3d206f46577050000000c0000526401025f00640203bb628e2902000000338c430100000000320000",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 2",
|
||||
hexData: "e517cb977ae3ad2a04000000642300024b00000000410002761acfb15ea9fdcd0501200204769358e96343759bf8014402046196591e1e020000f5bf6fe101000000d00700",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
instrData, err := hex.DecodeString(tt.hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("raw bytes: %x", instrData[8:])
|
||||
args, err := decodeJupiterV6RouteArg(instrData[8:])
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode jupiter arguments: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("decoded args: %+v", args)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParseJupiterV6RouteDoesNotMisclassifyMeteoraDlmmAsPump(t *testing.T) {
|
||||
instrData, err := hex.DecodeString("e517cb977ae3ad2a0100000026640001b9fe480300000000187dbe35000000002c0100")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode instruction data: %v", err)
|
||||
}
|
||||
|
||||
accountStrs := []string{
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
||||
"7Ubi7vPnj5E2WdMtpA21mQoZmpY2TnrDqWpHJg2j2k7z",
|
||||
"F7GdscGrjA8YwmiRaJNnbFAtmU6pmncwJX2Q6TNEVtWJ",
|
||||
"4tBgpAzd4QRCSKDkmSaRVqWpnKmRJjT5djjhaspNxUWR",
|
||||
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
|
||||
"CreiuhfwdWCN5mJbMJtA9bBpYQrQF2tCBuZwSPWfpump",
|
||||
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
|
||||
"D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf",
|
||||
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
|
||||
"LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
|
||||
"6bh2zL833toG7TnbBn2HEi6vLBbMTEwREk6YPZYQThDK",
|
||||
"7Arfi1EzAiSMdbbwsEwUzndDGQzw1bHrVHir2cZmYi96",
|
||||
"8SQXJSUGrwRuh1fRtta2pGTXSKztboatnDjV9hdi7Kon",
|
||||
"G6SuCjTPhddLpUd7uUh5NJCpEhfg5oeN5vKxYmvnDX6i",
|
||||
"F7GdscGrjA8YwmiRaJNnbFAtmU6pmncwJX2Q6TNEVtWJ",
|
||||
"4tBgpAzd4QRCSKDkmSaRVqWpnKmRJjT5djjhaspNxUWR",
|
||||
"CreiuhfwdWCN5mJbMJtA9bBpYQrQF2tCBuZwSPWfpump",
|
||||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
||||
"9CuRuTiaNQjKyX73iPGY2n3qUZmP2Fyk4RvXKaHEksqj",
|
||||
"LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
|
||||
"7Ubi7vPnj5E2WdMtpA21mQoZmpY2TnrDqWpHJg2j2k7z",
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
||||
"D1ZN9Wj1fRSUQfCjhvnu1hqDMT7hzjzBBpi12nVniYD6",
|
||||
"LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
|
||||
"BAxxa7cjHvqbSGpowcpinGxNKN66B9vwsMhSNzUWuS69",
|
||||
"99nHJNiPJBBupPfK7jdEsvx9KoHxSbeqSAfyn2qgboFA",
|
||||
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
|
||||
}
|
||||
|
||||
accounts := make([]solana.PublicKey, 0, len(accountStrs))
|
||||
accountIndexes := make([]uint8, 0, len(accountStrs))
|
||||
for i, account := range accountStrs {
|
||||
accounts = append(accounts, solana.MustPublicKeyFromBase58(account))
|
||||
accountIndexes = append(accountIndexes, uint8(i))
|
||||
}
|
||||
|
||||
tx := VersionedTransaction{
|
||||
Signatures: []solana.Signature{
|
||||
solana.MustSignatureFromBase58("3AJSh1Dv4MHQL8UKLiVRkbAX2D45VRtNro68am9Dd66kh89khuFQGtsf8x1yx6m3pGSXU8vagb7Q4YfGXsfMzgEy"),
|
||||
},
|
||||
StaticAccountKeys: accounts,
|
||||
Instructions: []Instructions{
|
||||
{
|
||||
ProgramIDIndex: 4,
|
||||
Accounts: accountIndexes,
|
||||
Data: instrData,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
signals, err := parseJupiterV6Instruction(tx, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("parseJupiterV6Instruction returned error: %v", err)
|
||||
}
|
||||
if len(signals) != 0 {
|
||||
t.Fatalf("expected no signal for Meteora DLMM route, got %+v", signals)
|
||||
}
|
||||
}
|
||||
331
pkg/shreder/program_maestro.go
Normal file
331
pkg/shreder/program_maestro.go
Normal file
@@ -0,0 +1,331 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
)
|
||||
|
||||
var (
|
||||
maestroProgramId = solana.MustPublicKeyFromBase58("MaestroAAe9ge5HTc64VbBQZ6fP77pwvrhM8i1XWSAx")
|
||||
maestroMultiSwap2Discriminator = [8]byte{132, 9, 212, 45, 39, 113, 215, 54}
|
||||
)
|
||||
|
||||
type MaestroMultiSwap2Route struct {
|
||||
DexID uint8
|
||||
RouteKeyIdx uint8
|
||||
}
|
||||
|
||||
// MaestroMultiSwap2Args is the decoded payload of multi_swap2 instruction.
|
||||
// Payload layout (without 8-byte discriminator):
|
||||
// amount_in(u64), min_amount_out(u64), route0_weight(u16), route_len(u32),
|
||||
// routes(route_len * {dex_id(u8), reserved(u8), route_key_idx(u8)}), route_family(u8), route_flags(u8)
|
||||
type MaestroMultiSwap2Args struct {
|
||||
HasDiscriminator bool
|
||||
AmountIn uint64
|
||||
MinAmountOut uint64
|
||||
SlippageBps uint16
|
||||
RouteLen uint32
|
||||
Routes []MaestroMultiSwap2Route
|
||||
RouteFamily uint8
|
||||
RouteFlags uint8
|
||||
Extra []byte
|
||||
}
|
||||
|
||||
// decodeMaestroMultiSwap2Args decodes instruction bytes with or without the 8-byte multi_swap2 discriminator.
|
||||
func decodeMaestroMultiSwap2Args(data []byte) (*MaestroMultiSwap2Args, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("empty data")
|
||||
}
|
||||
|
||||
payload := data
|
||||
out := &MaestroMultiSwap2Args{}
|
||||
|
||||
if len(data) >= len(maestroMultiSwap2Discriminator) && bytes.Equal(data[:8], maestroMultiSwap2Discriminator[:]) {
|
||||
out.HasDiscriminator = true
|
||||
payload = data[8:]
|
||||
}
|
||||
|
||||
const minPayloadLen = 24 // fixed fields + route_family + route_flags when route_len==0
|
||||
if len(payload) < minPayloadLen {
|
||||
return nil, fmt.Errorf("payload too short: got %d, need at least %d", len(payload), minPayloadLen)
|
||||
}
|
||||
|
||||
out.AmountIn = binary.LittleEndian.Uint64(payload[0:8])
|
||||
out.MinAmountOut = binary.LittleEndian.Uint64(payload[8:16])
|
||||
out.SlippageBps = binary.LittleEndian.Uint16(payload[16:18])
|
||||
out.RouteLen = binary.LittleEndian.Uint32(payload[18:22])
|
||||
|
||||
needed := uint64(minPayloadLen) + uint64(out.RouteLen)*3
|
||||
if needed > uint64(len(payload)) {
|
||||
return nil, fmt.Errorf("payload too short for routes: got %d, need %d (route_len=%d)", len(payload), needed, out.RouteLen)
|
||||
}
|
||||
|
||||
offset := 22
|
||||
out.Routes = make([]MaestroMultiSwap2Route, 0, out.RouteLen)
|
||||
for i := uint32(0); i < out.RouteLen; i++ {
|
||||
route := MaestroMultiSwap2Route{
|
||||
DexID: payload[offset],
|
||||
RouteKeyIdx: payload[offset+2],
|
||||
}
|
||||
out.Routes = append(out.Routes, route)
|
||||
offset += 3
|
||||
}
|
||||
|
||||
out.RouteFamily = payload[offset]
|
||||
out.RouteFlags = payload[offset+1]
|
||||
offset += 2
|
||||
|
||||
if len(payload) > offset {
|
||||
out.Extra = append([]byte(nil), payload[offset:]...)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// maestroMultiSwap2DexName maps observed dex ids from MultiSwap2 routes.
|
||||
func maestroMultiSwap2DexName(dexID uint8) string {
|
||||
switch dexID {
|
||||
case 0:
|
||||
return "RaydiumV4"
|
||||
case 1:
|
||||
return "MeteoraDLMM"
|
||||
case 3:
|
||||
return "RaydiumLaunchLab"
|
||||
case 4:
|
||||
return "PumpAMM"
|
||||
case 5:
|
||||
return "RaydiumCPMM"
|
||||
case 6:
|
||||
return "MeteoraAmmV2"
|
||||
case 7:
|
||||
return "RaydiumCLMM"
|
||||
case 8:
|
||||
return "OrcaWhirPool"
|
||||
case 9:
|
||||
return "MeteoraPools"
|
||||
case 10:
|
||||
return "MeteoraDynamicBondingCurve"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown(%d)", dexID)
|
||||
}
|
||||
}
|
||||
|
||||
func parseMaestroInstruction(tx VersionedTransaction, idx int) (TxSignalBatch, error) {
|
||||
if idx >= len(tx.Instructions) {
|
||||
return nil, fmt.Errorf("instruction index out of bounds")
|
||||
}
|
||||
ix := tx.Instructions[idx]
|
||||
if len(ix.Data) < 8 {
|
||||
return nil, nil
|
||||
}
|
||||
return parseMaestroInstructionDataAndAccounts(tx, ix.Data, ix.Accounts)
|
||||
}
|
||||
|
||||
func parsePumpAMMMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixAccounts []uint8, args *MaestroMultiSwap2Args) (TxSignalBatch, error) {
|
||||
var (
|
||||
event string
|
||||
token0Amount uint64
|
||||
token1Amount uint64
|
||||
|
||||
// pool solana.PublicKey
|
||||
baseMint solana.PublicKey
|
||||
quoteMint solana.PublicKey
|
||||
)
|
||||
|
||||
routeFlag := args.Routes[0].RouteKeyIdx
|
||||
if routeFlag == 101 || routeFlag == 97 {
|
||||
event = "buy"
|
||||
token0Amount = args.MinAmountOut
|
||||
token1Amount = args.AmountIn
|
||||
} else if routeFlag == 65 || routeFlag == 71 {
|
||||
event = "sell"
|
||||
token0Amount = args.AmountIn
|
||||
token1Amount = args.MinAmountOut
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if routeFlag == 101 || routeFlag == 71 {
|
||||
if len(ixAccounts) < 22 {
|
||||
return nil, nil
|
||||
}
|
||||
token2022, err := tx.GetAccount(int(ixAccounts[6]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !token2022.Equals(solana.Token2022ProgramID) {
|
||||
return nil, nil
|
||||
}
|
||||
//pool, err = tx.GetAccount(int(ixAccounts[10]))
|
||||
if event == "buy" {
|
||||
quoteMint, err = tx.GetAccount(int(ixAccounts[9]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseMint, err = tx.GetAccount(int(ixAccounts[21]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
baseMint, err = tx.GetAccount(int(ixAccounts[9]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quoteMint, err = tx.GetAccount(int(ixAccounts[21]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
} else if routeFlag == 97 || routeFlag == 65 {
|
||||
if len(ixAccounts) < 21 {
|
||||
return nil, nil
|
||||
}
|
||||
tokenPro, err := tx.GetAccount(int(ixAccounts[5]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !tokenPro.Equals(solana.TokenProgramID) {
|
||||
return nil, nil
|
||||
}
|
||||
//pool, err = tx.GetAccount(int(ixAccounts[9]))
|
||||
if event == "buy" {
|
||||
quoteMint, err = tx.GetAccount(int(ixAccounts[8]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseMint, err = tx.GetAccount(int(ixAccounts[20]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
baseMint, err = tx.GetAccount(int(ixAccounts[8]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quoteMint, err = tx.GetAccount(int(ixAccounts[20]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if !quoteMint.Equals(wrappedSOL) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return TxSignalBatch{
|
||||
&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: baseMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(token0Amount),
|
||||
Token1Amount: formatTokenAmount(token1Amount),
|
||||
Event: event,
|
||||
Program: "PumpAMM",
|
||||
IsProcessed: false,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: event == "buy",
|
||||
Token0AmountUint64: token0Amount,
|
||||
Token1AmountUint64: token1Amount,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseRaydiumLaunchLabMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixAccounts []uint8, args *MaestroMultiSwap2Args) (TxSignalBatch, error) {
|
||||
var (
|
||||
event string
|
||||
token0Amount uint64
|
||||
token1Amount uint64
|
||||
|
||||
// pool solana.PublicKey
|
||||
baseMint solana.PublicKey
|
||||
quoteMint solana.PublicKey
|
||||
|
||||
err error
|
||||
)
|
||||
|
||||
routeFlag := args.Routes[0].RouteKeyIdx
|
||||
if routeFlag == 0x0b {
|
||||
event = "buy"
|
||||
token0Amount = args.MinAmountOut
|
||||
token1Amount = args.AmountIn
|
||||
if len(ixAccounts) < 18 {
|
||||
return nil, nil
|
||||
}
|
||||
quoteMint, err = tx.GetAccount(int(ixAccounts[7]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseMint, err = tx.GetAccount(int(ixAccounts[17]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if routeFlag == 0x08 {
|
||||
event = "sell"
|
||||
token0Amount = args.AmountIn
|
||||
token1Amount = args.MinAmountOut
|
||||
if len(ixAccounts) < 18 {
|
||||
return nil, nil
|
||||
}
|
||||
quoteMint, err = tx.GetAccount(int(ixAccounts[17]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseMint, err = tx.GetAccount(int(ixAccounts[7]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !quoteMint.Equals(wrappedSOL) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return TxSignalBatch{
|
||||
&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: baseMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(token0Amount),
|
||||
Token1Amount: formatTokenAmount(token1Amount),
|
||||
Event: event,
|
||||
Program: "RaydiumLaunchLab",
|
||||
ExactSOL: event == "buy",
|
||||
Token0AmountUint64: token0Amount,
|
||||
Token1AmountUint64: token1Amount,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseMaestroInstructionDataAndAccounts(tx VersionedTransaction, ixData []byte, ixAccounts []uint8) (TxSignalBatch, error) {
|
||||
args, err := decodeMaestroMultiSwap2Args(ixData)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
// only decode 1 route
|
||||
if len(args.Routes) != 1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if args.Routes[0].DexID == 4 {
|
||||
return parsePumpAMMMaestroInstructionDataAndAccounts(tx, ixAccounts, args)
|
||||
} else if args.Routes[0].DexID == 3 {
|
||||
return parseRaydiumLaunchLabMaestroInstructionDataAndAccounts(tx, ixAccounts, args)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
495
pkg/shreder/program_okxonchainlab.go
Normal file
495
pkg/shreder/program_okxonchainlab.go
Normal file
@@ -0,0 +1,495 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
bin "github.com/gagliardetto/binary"
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var (
|
||||
okxDexRouteV2ProgramID = solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u")
|
||||
okxDexRouteV2ProgramIDString = okxDexRouteV2ProgramID.String()
|
||||
|
||||
okxSwapTobDisc = []byte{170, 41, 85, 177, 132, 80, 31, 53}
|
||||
okxSwapTobWithReceiverDisc = []byte{223, 170, 216, 234, 204, 6, 241, 25}
|
||||
okxSwapTocDisc = []byte{187, 201, 212, 51, 16, 155, 236, 60}
|
||||
okxSwapTocV2Disc = []byte{127, 214, 107, 189, 23, 90, 47, 104}
|
||||
)
|
||||
|
||||
// IDL: SwapArgs { order_id:u64, amount_in:u64, expect_amount_out:u64, slippage:u16, routes: Vec<Route> }
|
||||
// IDL: Route { dex: Dex(enum), weight:u16, index:u8 }
|
||||
|
||||
type OkxV2Route struct {
|
||||
Dex OkxV2SwapKind
|
||||
Weight uint16
|
||||
Index uint8
|
||||
}
|
||||
|
||||
type OkxV2SwapArgs struct {
|
||||
OrderID uint64
|
||||
AmountIn uint64
|
||||
ExpectAmountOut uint64
|
||||
Slippage uint16
|
||||
Routes []OkxV2Route
|
||||
}
|
||||
|
||||
type OkxV2SwapKind uint8
|
||||
|
||||
const (
|
||||
OKCV2_SplTokenSwap OkxV2SwapKind = iota
|
||||
OKCV2_StableSwap
|
||||
OKCV2_Whirlpool
|
||||
OKCV2_MeteoraDynamicpool
|
||||
OKCV2_RaydiumSwap
|
||||
OKCV2_RaydiumStableSwap
|
||||
OKCV2_RaydiumClmmSwap
|
||||
OKCV2_AldrinExchangeV1
|
||||
OKCV2_AldrinExchangeV2
|
||||
OKCV2_LifinityV1
|
||||
OKCV2_LifinityV2
|
||||
OKCV2_RaydiumClmmSwapV2
|
||||
OKCV2_FluxBeam
|
||||
OKCV2_MeteoraDlmm
|
||||
OKCV2_RaydiumCpmmSwap
|
||||
OKCV2_OpenBookV2
|
||||
OKCV2_WhirlpoolV2
|
||||
OKCV2_Phoenix
|
||||
OKCV2_ObricV2
|
||||
OKCV2_SanctumAddLiq
|
||||
OKCV2_SanctumRemoveLiq
|
||||
OKCV2_SanctumNonWsolSwap
|
||||
OKCV2_SanctumWsolSwap
|
||||
OKCV2_PumpfunBuy
|
||||
OKCV2_PumpfunSell
|
||||
OKCV2_StabbleSwap
|
||||
OKCV2_SanctumRouter
|
||||
OKCV2_MeteoraVaultDeposit
|
||||
OKCV2_MeteoraVaultWithdraw
|
||||
OKCV2_Saros
|
||||
OKCV2_MeteoraLst
|
||||
OKCV2_Solfi
|
||||
OKCV2_QualiaSwap
|
||||
OKCV2_Zerofi
|
||||
OKCV2_PumpfunammBuy
|
||||
OKCV2_PumpfunammSell
|
||||
OKCV2_Virtuals
|
||||
OKCV2_VertigoBuy
|
||||
OKCV2_VertigoSell
|
||||
OKCV2_PerpetualsAddLiq
|
||||
OKCV2_PerpetualsRemoveLiq
|
||||
OKCV2_PerpetualsSwap
|
||||
OKCV2_RaydiumLaunchpad
|
||||
OKCV2_LetsBonkFun
|
||||
OKCV2_Woofi
|
||||
OKCV2_MeteoraDbc
|
||||
OKCV2_MeteoraDlmmSwap2
|
||||
OKCV2_MeteoraDAMMV2
|
||||
OKCV2_Gavel
|
||||
OKCV2_BoopfunBuy
|
||||
OKCV2_BoopfunSell
|
||||
OKCV2_MeteoraDbc2
|
||||
OKCV2_GooseFX
|
||||
OKCV2_Dooar
|
||||
OKCV2_Numeraire
|
||||
OKCV2_SaberDecimalWrapperDeposit
|
||||
OKCV2_SaberDecimalWrapperWithdraw
|
||||
OKCV2_SarosDlmm
|
||||
OKCV2_OneDexSwap
|
||||
OKCV2_Manifest
|
||||
OKCV2_ByrealClmm
|
||||
OKCV2_PancakeSwapV3Swap
|
||||
OKCV2_PancakeSwapV3SwapV2
|
||||
OKCV2_Tessera
|
||||
OKCV2_SolRfq
|
||||
OKCV2_Humidifi
|
||||
OKCV2_HeavenBuy
|
||||
OKCV2_HeavenSell
|
||||
OKCV2_SolfiV2
|
||||
OKCV2_Goonfi
|
||||
OKCV2_MoonitBuy
|
||||
OKCV2_MoonitSell
|
||||
OKCV2_RaydiumSwapV2
|
||||
OKCV2_Whalestreet
|
||||
OKCV2_SugarMoneyBuy
|
||||
OKCV2_SugarMoneySell
|
||||
OKCV2_MeteoraDAMMV2Swap2
|
||||
OKCV2_AlphaQ
|
||||
OKCV2_FutarchyAmm
|
||||
OKCV2_PumpfunBuy2
|
||||
OKCV2_PumpfunSell2
|
||||
OKCV2_HumidifiSwap2
|
||||
OKCV2_Scorch
|
||||
OKCV2_JupiterLendDeposit
|
||||
OKCV2_JupiterLendRedeem
|
||||
OKCV2_TokkaAmm
|
||||
)
|
||||
|
||||
func decodeOkxSwapTobSwapArgs(data []byte) (*OkxV2SwapArgs, error) {
|
||||
dec := bin.NewBorshDecoder(data)
|
||||
return decodeOkxV2SwapArgs(dec)
|
||||
}
|
||||
|
||||
func decodeOkxSwapTobWithReceiverSwapArgs(data []byte) (*OkxV2SwapArgs, error) {
|
||||
dec := bin.NewBorshDecoder(data)
|
||||
return decodeOkxV2SwapArgs(dec)
|
||||
}
|
||||
|
||||
func decodeOkxSwapTocSwapArgs(data []byte) (*OkxV2SwapArgs, error) {
|
||||
dec := bin.NewBorshDecoder(data)
|
||||
return decodeOkxV2SwapArgs(dec)
|
||||
}
|
||||
|
||||
func decodeOkxSwapTocV2SwapArgs(data []byte) (*OkxV2SwapArgs, error) {
|
||||
dec := bin.NewBorshDecoder(data)
|
||||
return decodeOkxV2SwapArgs(dec)
|
||||
}
|
||||
|
||||
func skipOkxV2DexPayload(dec *bin.Decoder, dex OkxV2SwapKind) error {
|
||||
// IMPORTANT: In IDL, Dex is an enum. Most variants have no fields, but some carry payload.
|
||||
// We only need to keep decoding aligned for SwapArgs.routes.
|
||||
switch dex {
|
||||
case OKCV2_SolRfq:
|
||||
// fields: 6*u64 + 2*bool
|
||||
// rfq_id, expected_maker_amount, expected_taker_amount, maker_send_amount,
|
||||
// taker_send_amount, expiry, maker_use_native_sol, taker_use_native_sol
|
||||
if err := dec.SkipBytes(8 * 6); err != nil {
|
||||
return err
|
||||
}
|
||||
return dec.SkipBytes(2)
|
||||
case OKCV2_SugarMoneyBuy, OKCV2_SugarMoneySell:
|
||||
// fields: u8 + u8
|
||||
return dec.SkipBytes(2)
|
||||
case OKCV2_HumidifiSwap2:
|
||||
// fields: u64
|
||||
return dec.SkipBytes(8)
|
||||
case OKCV2_Scorch:
|
||||
// fields: u128 => 16 bytes
|
||||
return dec.SkipBytes(16)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func decodeOkxV2SwapArgs(dec *bin.Decoder) (*OkxV2SwapArgs, error) {
|
||||
out := &OkxV2SwapArgs{}
|
||||
var err error
|
||||
|
||||
if out.OrderID, err = dec.ReadUint64(binary.LittleEndian); err != nil {
|
||||
return nil, fmt.Errorf("read order_id: %w", err)
|
||||
}
|
||||
if out.AmountIn, err = dec.ReadUint64(binary.LittleEndian); err != nil {
|
||||
return nil, fmt.Errorf("read amount_in: %w", err)
|
||||
}
|
||||
if out.ExpectAmountOut, err = dec.ReadUint64(binary.LittleEndian); err != nil {
|
||||
return nil, fmt.Errorf("read expect_amount_out: %w", err)
|
||||
}
|
||||
if out.Slippage, err = dec.ReadUint16(binary.LittleEndian); err != nil {
|
||||
return nil, fmt.Errorf("read slippage: %w", err)
|
||||
}
|
||||
|
||||
// routes: Vec<Route>
|
||||
routesLen, err := dec.ReadUint32(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read routes len: %w", err)
|
||||
}
|
||||
out.Routes = make([]OkxV2Route, 0, routesLen)
|
||||
for i := uint32(0); i < routesLen; i++ {
|
||||
// Route { dex: Dex(enum tag u8 [+ payload]), weight: u16, index: u8 }
|
||||
tag, err := dec.ReadUint8()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read routes[%d].dex: %w", i, err)
|
||||
}
|
||||
dex := OkxV2SwapKind(tag)
|
||||
if err := skipOkxV2DexPayload(dec, dex); err != nil {
|
||||
return nil, fmt.Errorf("skip routes[%d].dex payload (%d): %w", i, tag, err)
|
||||
}
|
||||
weight, err := dec.ReadUint16(binary.LittleEndian)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read routes[%d].weight: %w", i, err)
|
||||
}
|
||||
idx, err := dec.ReadUint8()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read routes[%d].index: %w", i, err)
|
||||
}
|
||||
out.Routes = append(out.Routes, OkxV2Route{Dex: dex, Weight: weight, Index: idx})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type OkxV2SwapSolRfq struct {
|
||||
RfqId uint64
|
||||
expectedMakerAmount uint64
|
||||
expectedTakerAmount uint64
|
||||
makerSendAmount uint64
|
||||
takerSendAmount uint64
|
||||
expiry uint64
|
||||
makerUseNativeSol bool
|
||||
takerUseNativeSol bool
|
||||
}
|
||||
type OkxV2SwapSugarMoney struct {
|
||||
BondingCurveBump uint8
|
||||
|
||||
BondingCurveSolAssociatedAccountBump uint8
|
||||
}
|
||||
|
||||
type OkxV2SwapHumidifiSwap2 struct {
|
||||
SwapId uint64
|
||||
}
|
||||
|
||||
type OkxV2SwapScorch struct {
|
||||
Id [16]byte
|
||||
}
|
||||
|
||||
func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
|
||||
if instructionIndex >= len(tx.Instructions) {
|
||||
return nil, fmt.Errorf("instruction index out of bounds")
|
||||
}
|
||||
ix := tx.Instructions[instructionIndex]
|
||||
if len(ix.Data) < 8 {
|
||||
return nil, nil
|
||||
}
|
||||
disc := ix.Data[:8]
|
||||
data := ix.Data[8:]
|
||||
|
||||
var (
|
||||
args *OkxV2SwapArgs
|
||||
err error
|
||||
)
|
||||
switch {
|
||||
case bytes.Equal(disc, okxSwapTobDisc):
|
||||
args, err = decodeOkxSwapTobSwapArgs(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode swap_tob args: %w", err)
|
||||
}
|
||||
|
||||
case bytes.Equal(disc, okxSwapTobWithReceiverDisc):
|
||||
args, err = decodeOkxSwapTobWithReceiverSwapArgs(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode swap_tob_with_receiver args: %w", err)
|
||||
}
|
||||
case bytes.Equal(disc, okxSwapTocDisc):
|
||||
args, err = decodeOkxSwapTocSwapArgs(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode swap_toc args: %w", err)
|
||||
}
|
||||
|
||||
case bytes.Equal(disc, okxSwapTocV2Disc):
|
||||
args, err = decodeOkxSwapTocV2SwapArgs(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode swap_toc_v2 args: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
if len(ix.Accounts) < 15 {
|
||||
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
|
||||
}
|
||||
var (
|
||||
pumpAmmSellAmount uint64
|
||||
pumpAmmBuyAmount uint64
|
||||
pumpFunSellAmount uint64
|
||||
pumpFunBuyAmount uint64
|
||||
pumpAmmSellCount int
|
||||
pumpAmmBuyCount int
|
||||
pumpFunSellCount int
|
||||
pumpFunBuyCount int
|
||||
)
|
||||
for _, route := range args.Routes {
|
||||
if route.Index != 1 {
|
||||
continue
|
||||
}
|
||||
switch route.Dex {
|
||||
case OKCV2_PumpfunammSell:
|
||||
pumpAmmSellCount++
|
||||
pumpAmmSellAmount = args.AmountIn * uint64(route.Weight) / 10000
|
||||
case OKCV2_PumpfunammBuy:
|
||||
pumpAmmBuyCount++
|
||||
pumpAmmBuyAmount = args.AmountIn * uint64(route.Weight) / 10000
|
||||
case OKCV2_PumpfunSell2:
|
||||
pumpFunSellCount++
|
||||
pumpFunSellAmount = args.AmountIn * uint64(route.Weight) / 10000
|
||||
case OKCV2_PumpfunBuy2:
|
||||
pumpFunBuyCount++
|
||||
pumpFunBuyAmount = args.AmountIn * uint64(route.Weight) / 10000
|
||||
}
|
||||
}
|
||||
if pumpAmmSellCount > 1 {
|
||||
logger.Warn("pumpAmmSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpAmmSellCount)
|
||||
return nil, nil
|
||||
}
|
||||
if pumpAmmBuyCount > 1 {
|
||||
logger.Warn("pumpAmmSwapBuy at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpAmmBuyCount)
|
||||
return nil, nil
|
||||
}
|
||||
if pumpFunSellCount > 1 {
|
||||
logger.Warn("pumpFunSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpFunSellCount)
|
||||
return nil, nil
|
||||
}
|
||||
if pumpFunBuyCount > 1 {
|
||||
logger.Warn("pumpFunSwapBuy at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpFunBuyCount)
|
||||
return nil, nil
|
||||
}
|
||||
if pumpAmmSellAmount == 0 && pumpAmmBuyAmount == 0 && pumpFunSellAmount == 0 && pumpFunBuyAmount == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out := make(TxSignalBatch, 0, 2)
|
||||
|
||||
if pumpFunBuyAmount > 0 || pumpFunSellAmount > 0 {
|
||||
if pumpFunBuyAmount > 0 {
|
||||
if len(ix.Accounts) < 5 {
|
||||
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
|
||||
}
|
||||
baseMint, err := tx.GetAccount(int(ix.Accounts[4]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: baseMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: decimal.Zero,
|
||||
Token1Amount: formatSolAmount(pumpFunBuyAmount),
|
||||
Event: "buy",
|
||||
Program: "Pump",
|
||||
IsProcessed: false,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: true,
|
||||
Token0AmountUint64: 0,
|
||||
Token1AmountUint64: pumpFunBuyAmount,
|
||||
})
|
||||
}
|
||||
if pumpFunSellAmount > 0 {
|
||||
if len(ix.Accounts) < 4 {
|
||||
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
|
||||
}
|
||||
baseMint, err := tx.GetAccount(int(ix.Accounts[3]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: baseMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(pumpFunSellAmount),
|
||||
Token1Amount: decimal.Zero,
|
||||
Event: "sell",
|
||||
Program: "Pump",
|
||||
IsProcessed: false,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Token0AmountUint64: pumpFunSellAmount,
|
||||
Token1AmountUint64: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if pumpAmmBuyAmount > 0 || pumpAmmSellAmount > 0 {
|
||||
if len(ix.Accounts) <= 15 {
|
||||
if len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
accounts := ix.Accounts[14:]
|
||||
var pumpAmmIdx uint8
|
||||
for i, acctIdx := range accounts {
|
||||
key, err := tx.GetAccount(int(acctIdx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.Equals(pumpAmmProgramID) {
|
||||
pumpAmmIdx = uint8(i + 6)
|
||||
break
|
||||
}
|
||||
}
|
||||
if pumpAmmIdx == 0 || int(pumpAmmIdx+1) >= len(accounts) {
|
||||
if len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
baseMint, err := tx.GetAccount(int(accounts[pumpAmmIdx]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quoteMint, err := tx.GetAccount(int(accounts[pumpAmmIdx+1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !quoteMint.Equals(solana.WrappedSol) {
|
||||
if len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
if pumpAmmBuyAmount > 0 {
|
||||
if len(ix.Accounts) < 5 {
|
||||
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
|
||||
}
|
||||
srcMint, err := tx.GetAccount(int(ix.Accounts[4]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if baseMint.Equals(srcMint) {
|
||||
out = append(out, &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: baseMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: decimal.Zero,
|
||||
Token1Amount: formatSolAmount(pumpAmmBuyAmount),
|
||||
Event: "buy",
|
||||
Program: "PumpAMM",
|
||||
IsProcessed: false,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: true,
|
||||
Token0AmountUint64: 0,
|
||||
Token1AmountUint64: pumpAmmBuyAmount,
|
||||
})
|
||||
}
|
||||
} else if pumpAmmSellAmount > 0 {
|
||||
if len(ix.Accounts) < 4 {
|
||||
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
|
||||
}
|
||||
srcMint, err := tx.GetAccount(int(ix.Accounts[3]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if baseMint.Equals(srcMint) {
|
||||
out = append(out, &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: baseMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(pumpAmmSellAmount),
|
||||
Token1Amount: decimal.Zero,
|
||||
Event: "sell",
|
||||
Program: "PumpAMM",
|
||||
IsProcessed: false,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Token0AmountUint64: pumpAmmSellAmount,
|
||||
Token1AmountUint64: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
156
pkg/shreder/program_photon.go
Normal file
156
pkg/shreder/program_photon.go
Normal 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
|
||||
}
|
||||
276
pkg/shreder/program_pump.go
Normal file
276
pkg/shreder/program_pump.go
Normal file
@@ -0,0 +1,276 @@
|
||||
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
|
||||
IsCashbackEnabled bool
|
||||
}
|
||||
|
||||
type legacyPumpCreateCoinV2Args 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 {
|
||||
var legacyArgs legacyPumpCreateCoinV2Args
|
||||
if err := borsh.Deserialize(&legacyArgs, instruction.Data[8:]); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse create coin v2 args: %w", err)
|
||||
}
|
||||
args = pumpCreateCoinV2Args{
|
||||
Name: legacyArgs.Name,
|
||||
Symbol: legacyArgs.Symbol,
|
||||
Uri: legacyArgs.Uri,
|
||||
Creator: legacyArgs.Creator,
|
||||
IsMayhemMode: legacyArgs.IsMayhemMode,
|
||||
IsCashbackEnabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
IsCashbackEnabled: args.IsCashbackEnabled,
|
||||
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
|
||||
}
|
||||
166
pkg/shreder/program_pumpamm.go
Normal file
166
pkg/shreder/program_pumpamm.go
Normal 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
166
pkg/shreder/program_qtkv.go
Normal 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
|
||||
}
|
||||
204
pkg/shreder/program_raydiumlaunchlab.go
Normal file
204
pkg/shreder/program_raydiumlaunchlab.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var raydiumLaunchLabProgramID = solana.MustPublicKeyFromBase58("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
|
||||
var (
|
||||
raydiumLaunchLabInitializeV2PoolDiscriminator = []byte{67, 153, 175, 39, 218, 16, 38, 32}
|
||||
raydiumLaunchLabInitializeWithToken2022PoolDiscriminator = []byte{37, 190, 126, 222, 44, 154, 171, 17}
|
||||
raydiumLaunchLabSellExactInDiscriminator = []byte{0x95, 0x27, 0xde, 0x9b, 0xd3, 0x7c, 0x98, 0x1a}
|
||||
raydiumLaunchLabSellExactOutDiscriminator = []byte{0x5f, 0xc8, 0x47, 0x22, 0x08, 0x09, 0x0b, 0xa6}
|
||||
raydiumLaunchLabBuyExactInDiscriminator = []byte{0xfa, 0xea, 0x0d, 0x7b, 0xd5, 0x9c, 0x13, 0xec}
|
||||
raydiumLaunchLabBuyExactOutDiscriminator = []byte{0x18, 0xd3, 0x74, 0x28, 0x69, 0x03, 0x99, 0x38}
|
||||
)
|
||||
|
||||
func parseRaydiumLaunchLabInstruction(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, raydiumLaunchLabBuyExactInDiscriminator) {
|
||||
txSignal, err = parseRaydiumLaunchLabSwap(tx, instruction, true, true)
|
||||
} else if matchMethod(instruction.Data, raydiumLaunchLabBuyExactOutDiscriminator) {
|
||||
txSignal, err = parseRaydiumLaunchLabSwap(tx, instruction, true, false)
|
||||
} else if matchMethod(instruction.Data, raydiumLaunchLabSellExactInDiscriminator) {
|
||||
txSignal, err = parseRaydiumLaunchLabSwap(tx, instruction, false, true)
|
||||
} else if matchMethod(instruction.Data, raydiumLaunchLabSellExactOutDiscriminator) {
|
||||
txSignal, err = parseRaydiumLaunchLabSwap(tx, instruction, false, false)
|
||||
} else if matchMethod(instruction.Data, raydiumLaunchLabInitializeV2PoolDiscriminator) {
|
||||
txSignal, err = parseRaydiumLaunchLabCreate(tx, instruction, false)
|
||||
} else if matchMethod(instruction.Data, raydiumLaunchLabInitializeWithToken2022PoolDiscriminator) {
|
||||
txSignal, err = parseRaydiumLaunchLabCreate(tx, instruction, true)
|
||||
}
|
||||
if txSignal != nil {
|
||||
return TxSignalBatch{txSignal}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func parseRaydiumLaunchLabCreate(tx VersionedTransaction, instruction Instructions, isToken2022 bool) (*TxSignal, error) {
|
||||
if len(instruction.Accounts) < 10 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
base, err := tx.GetAccount(int(instruction.Accounts[6]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quote, err := tx.GetAccount(int(instruction.Accounts[7]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creator, err := tx.GetAccount(int(instruction.Accounts[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !quote.Equals(wrappedSOL) {
|
||||
// just ignore this
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "raydiumlaunchlab",
|
||||
Maker: creator.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: decimal.Zero,
|
||||
Token1Amount: decimal.Zero,
|
||||
Program: "RaydiumLaunchLab",
|
||||
Event: "create",
|
||||
IsToken2022: isToken2022,
|
||||
IsMayhemMode: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: 0,
|
||||
Token1AmountUint64: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseRaydiumLaunchLabSwap(tx VersionedTransaction, instruction Instructions, isBuy bool, exactIn bool) (*TxSignal, error) {
|
||||
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 raydium launch lab swap args, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
base, err := tx.GetAccount(int(instruction.Accounts[9]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quote, 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
|
||||
}
|
||||
|
||||
if !quote.Equals(wrappedSOL) {
|
||||
// just ignore this
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
amountA := binary.LittleEndian.Uint64(instruction.Data[8:16])
|
||||
amountB := binary.LittleEndian.Uint64(instruction.Data[16:24])
|
||||
|
||||
if isBuy {
|
||||
if exactIn {
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "raydiumlaunchlab",
|
||||
Maker: user.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(amountB),
|
||||
Token1Amount: formatSolAmount(amountA),
|
||||
Program: "RaydiumLaunchLab",
|
||||
Event: "buy",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: true,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: amountB,
|
||||
Token1AmountUint64: amountA,
|
||||
}, nil
|
||||
} else {
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "raydiumlaunchlab",
|
||||
Maker: user.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(amountA),
|
||||
Token1Amount: formatSolAmount(amountB),
|
||||
Program: "RaydiumLaunchLab",
|
||||
Event: "buy",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: amountA,
|
||||
Token1AmountUint64: amountB,
|
||||
}, nil
|
||||
}
|
||||
} else {
|
||||
if exactIn {
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "raydiumlaunchlab",
|
||||
Maker: user.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(amountA),
|
||||
Token1Amount: formatSolAmount(amountB),
|
||||
Program: "RaydiumLaunchLab",
|
||||
Event: "sell",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: amountA,
|
||||
Token1AmountUint64: amountB,
|
||||
}, nil
|
||||
} else {
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "raydiumlaunchlab",
|
||||
Maker: user.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(amountB),
|
||||
Token1Amount: formatSolAmount(amountA),
|
||||
Program: "RaydiumLaunchLab",
|
||||
Event: "sell",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: true,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: amountB,
|
||||
Token1AmountUint64: amountA,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
155
pkg/shreder/program_term.go
Normal file
155
pkg/shreder/program_term.go
Normal 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{0x14, 0xfe, 0x38, 0xc9, 0x3d, 0x37, 0x17, 0x27}
|
||||
terminalSellTokensIX = []byte{0xcd, 0xaa, 0x10, 0x49, 0x20, 0xd6, 0x62, 0xd6}
|
||||
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: "sell",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: tokenAmount,
|
||||
Token1AmountUint64: solAmount,
|
||||
}, nil
|
||||
}
|
||||
50
pkg/shreder/program_tradewiz.go
Normal file
50
pkg/shreder/program_tradewiz.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var tradewizProgramID = solana.MustPublicKeyFromBase58("B3jytJa6Tzpn4Ly7GNnDm3dMGqUin5aMRm5aPsJGU5G7")
|
||||
|
||||
func parseTradewizInstruction(tx VersionedTransaction, instructionIndex int) (TxSignalBatch, error) {
|
||||
if instructionIndex >= len(tx.Instructions) {
|
||||
return nil, fmt.Errorf("instruction index out of bounds")
|
||||
}
|
||||
ix := tx.Instructions[instructionIndex]
|
||||
if len(ix.Data) < 9 {
|
||||
return nil, fmt.Errorf("data too short for tradewiz buy args, len=%d", len(ix.Data))
|
||||
}
|
||||
if len(ix.Accounts) < 3 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
// data format: 0x00 + u64(wsol amount) + u64(...)
|
||||
wsolAmount := binary.LittleEndian.Uint64(ix.Data[1:9])
|
||||
|
||||
mint, err := tx.GetAccount(int(ix.Accounts[2]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "tradewiz",
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: decimal.Zero,
|
||||
Token1Amount: formatSolAmount(wsolAmount),
|
||||
Program: "Pump",
|
||||
Event: "buy",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: true,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: 0,
|
||||
Token1AmountUint64: wsolAmount,
|
||||
}}, nil
|
||||
}
|
||||
@@ -44,20 +44,29 @@ type TxSignal struct {
|
||||
IsProcessed bool `json:"is_processed"`
|
||||
IsToken2022 bool `json:"is_token2022"`
|
||||
IsMayhemMode bool `json:"is_mayhem_mode"`
|
||||
TxFee decimal.Decimal `json:"tx_fee"`
|
||||
EntryContract string `json:"entry_contract"`
|
||||
IsCashbackEnabled bool `json:"is_cashback_enabled"`
|
||||
CUPrice decimal.Decimal `json:"cu_price"`
|
||||
CULimit uint32 `json:"cu_limit"`
|
||||
SWQoSAgent string `json:"swqos_agent"`
|
||||
SWQoSTips decimal.Decimal `json:"swqos_tips"`
|
||||
TableCnt int `json:"table_cnt"`
|
||||
|
||||
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"`
|
||||
//
|
||||
LbPairAddress string `json:"lb_pair_address"`
|
||||
|
||||
// parsed values
|
||||
Token0AmountUint64 uint64 `json:"-"`
|
||||
Token1AmountUint64 uint64 `json:"-"`
|
||||
}
|
||||
|
||||
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
|
||||
ParseStart time.Time `json:"parse_start"`
|
||||
ParseEnd time.Time `json:"parse_end"`
|
||||
}
|
||||
|
||||
type TxSignalBatch = []*TxSignal
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
62
pkg/shreder/versioned.go
Normal file
62
pkg/shreder/versioned.go
Normal 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))
|
||||
}
|
||||
205
pkg/swqos/clients/soyas_client.go
Normal file
205
pkg/swqos/clients/soyas_client.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package clients
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
const (
|
||||
alpnTPUProtocolID = "solana-tpu"
|
||||
defaultServerName = "soyas-landing"
|
||||
defaultKeepAlive = 25 * time.Second
|
||||
defaultIdleTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
type SoyasClient struct {
|
||||
endpointAddr string
|
||||
tlsConfig *tls.Config
|
||||
quicConfig *quic.Config
|
||||
|
||||
connMu sync.RWMutex
|
||||
conn *quic.Conn
|
||||
reconnectMu sync.Mutex
|
||||
}
|
||||
|
||||
// Connect creates a client using the whitelisted Solana keypair (base58-encoded secret key) as the mutual-TLS client identity.
|
||||
func NewSoyasClient(ctx context.Context, url string) *SoyasClient {
|
||||
cert, err := x509CertificateFromSolanaBase58Key("2ketcrBU1kBvr68sPVYdBdn5ztgg3VBKZP1xa1o5B8w47wemBXH73ZALdmj3ukcGzkxh6DhzLq3myu45XUwW1eNC")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ServerName: defaultServerName,
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{alpnTPUProtocolID},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
KeepAlivePeriod: defaultKeepAlive,
|
||||
MaxIdleTimeout: defaultIdleTimeout,
|
||||
}
|
||||
|
||||
client := &SoyasClient{
|
||||
endpointAddr: url,
|
||||
tlsConfig: tlsConfig,
|
||||
quicConfig: quicConfig,
|
||||
}
|
||||
|
||||
if err = client.reconnect(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// Close closes the underlying QUIC connection (if any). Safe to call multiple times.
|
||||
func (c *SoyasClient) Close() error {
|
||||
c.reconnectMu.Lock()
|
||||
defer c.reconnectMu.Unlock()
|
||||
|
||||
c.connMu.Lock()
|
||||
conn := c.conn
|
||||
c.conn = nil
|
||||
c.connMu.Unlock()
|
||||
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
return conn.CloseWithError(0, "")
|
||||
}
|
||||
|
||||
// SendTransaction sends a signed Solana transaction payload to Soyas.
|
||||
// The payload should be the raw wire bytes (for example, from solana-go's tx.MarshalBinary()).
|
||||
// If sending fails, it reconnects once and retries.
|
||||
func (c *SoyasClient) SendTransaction(ctx context.Context, tx *solana.Transaction) error {
|
||||
if c.endpointAddr == "" {
|
||||
return fmt.Errorf("send tx url is empty")
|
||||
}
|
||||
|
||||
raw, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := c.getConn()
|
||||
if conn != nil {
|
||||
if err := trySendBytes(ctx, conn, raw); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.reconnect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
conn = c.getConn()
|
||||
if conn == nil {
|
||||
return errors.New("missing QUIC connection")
|
||||
}
|
||||
return trySendBytes(ctx, conn, raw)
|
||||
}
|
||||
|
||||
func (c *SoyasClient) SendBundle(ctx context.Context, txs []*solana.Transaction) error {
|
||||
return fmt.Errorf("soyas client not support send bundle")
|
||||
}
|
||||
|
||||
func (c *SoyasClient) getConn() *quic.Conn {
|
||||
c.connMu.RLock()
|
||||
defer c.connMu.RUnlock()
|
||||
return c.conn
|
||||
}
|
||||
|
||||
func (c *SoyasClient) reconnect(ctx context.Context) error {
|
||||
c.reconnectMu.Lock()
|
||||
defer c.reconnectMu.Unlock()
|
||||
|
||||
if existing := c.getConn(); existing != nil && existing.Context().Err() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := quic.DialAddr(ctx, c.endpointAddr, c.tlsConfig, c.quicConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.connMu.Lock()
|
||||
old := c.conn
|
||||
c.conn = conn
|
||||
c.connMu.Unlock()
|
||||
|
||||
if old != nil {
|
||||
_ = old.CloseWithError(0, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func trySendBytes(ctx context.Context, conn *quic.Conn, payload []byte) error {
|
||||
stream, err := conn.OpenUniStreamSync(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := stream.Write(payload); err != nil {
|
||||
_ = stream.Close()
|
||||
return err
|
||||
}
|
||||
return stream.Close()
|
||||
}
|
||||
|
||||
// x509CertificateFromSolanaBase58Key creates a short-lived self-signed X.509
|
||||
// certificate whose public key is derived from the provided Solana Ed25519 key.
|
||||
// The Soyas ingress extracts this public key to identify/allowlist the client.
|
||||
func x509CertificateFromSolanaBase58Key(apiKeyBase58 string) (tls.Certificate, error) {
|
||||
raw, err := base58.Decode(apiKeyBase58)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
var seed []byte
|
||||
switch len(raw) {
|
||||
case ed25519.SeedSize:
|
||||
seed = raw
|
||||
case ed25519.PrivateKeySize:
|
||||
seed = raw[:ed25519.SeedSize]
|
||||
default:
|
||||
return tls.Certificate{}, errors.New("api key must decode to 32 (seed) or 64 (secret) bytes")
|
||||
}
|
||||
|
||||
priv := ed25519.NewKeyFromSeed(seed)
|
||||
pub := priv.Public().(ed25519.PublicKey)
|
||||
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
NotBefore: time.Now().Add(-5 * time.Minute),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
der, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
return tls.Certificate{
|
||||
Certificate: [][]byte{der},
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/samlior/libsam/pkg/enum"
|
||||
"github.com/samlior/libsam/pkg/swqos/clients"
|
||||
"github.com/samlior/libsam/v2/pkg/enum"
|
||||
"github.com/samlior/libsam/v2/pkg/swqos/clients"
|
||||
)
|
||||
|
||||
func NewSWQoSClient(ctx context.Context, config *SWQoSClientConfig) (SWQoSClient, error) {
|
||||
@@ -24,6 +24,8 @@ func NewSWQoSClient(ctx context.Context, config *SWQoSClientConfig) (SWQoSClient
|
||||
client = clients.NewAstralaneClient(config.SendTxUrl)
|
||||
case enum.SWQoSAgentBlocxRoute:
|
||||
client = clients.NewBloxrouteClient(config.SendTxUrl)
|
||||
case enum.SWQoSAgentSoyas:
|
||||
client = clients.NewSoyasClient(ctx, config.SendTxUrl)
|
||||
case enum.SWQoSAgent0slot, enum.SWQoSAgentJito, enum.SWQoSAgentHelius, enum.SWQoSAgentNozomi, enum.SWQoSAgentStellium:
|
||||
client = clients.NewHttpClient(config.SendTxUrl, config.SendBundleUrl)
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user