Merge branch 'master' of https://github.com/samlior/libsam
This commit is contained in:
1
pkg/shreder/OnChain_Labs_DexRouterV2-idl.json
Normal file
1
pkg/shreder/OnChain_Labs_DexRouterV2-idl.json
Normal file
File diff suppressed because one or more lines are too long
106
pkg/shreder/addresstables.go
Normal file
106
pkg/shreder/addresstables.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
type AddressTables struct {
|
||||
rpcClient *rpc.Client
|
||||
mux sync.RWMutex
|
||||
loadMux sync.Mutex
|
||||
tables *lru.Cache[solana.PublicKey, []solana.PublicKey]
|
||||
loading map[solana.PublicKey]struct{}
|
||||
|
||||
pool *ants.Pool
|
||||
}
|
||||
|
||||
func NewAddressTables(rpcClient *rpc.Client) *AddressTables {
|
||||
pool, _ := ants.NewPool(5, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
||||
cache, _ := lru.New[solana.PublicKey, []solana.PublicKey](10000)
|
||||
return &AddressTables{
|
||||
rpcClient: rpcClient,
|
||||
tables: cache,
|
||||
loading: make(map[solana.PublicKey]struct{}),
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
func (at *AddressTables) loadAddressTable(tablePubkey solana.PublicKey) ([]solana.PublicKey, error) {
|
||||
// decode acc
|
||||
acc, err := at.rpcClient.GetAccountInfoWithOpts(context.Background(), tablePubkey, &rpc.GetAccountInfoOpts{
|
||||
Encoding: solana.EncodingBase64,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := acc.GetBinary()
|
||||
if len(data) <= 56 {
|
||||
return nil, fmt.Errorf("account data too short")
|
||||
}
|
||||
offset := 56
|
||||
var addresses solana.PublicKeySlice = make([]solana.PublicKey, 0, (len(data)-offset)/32)
|
||||
for offset+32 <= len(data) {
|
||||
addresses = append(addresses, solana.PublicKeyFromBytes(data[offset:offset+32]))
|
||||
offset += 32
|
||||
}
|
||||
// addresses = append(addresses, solana.PublicKeyFromBytes(data[start:start+32]))
|
||||
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()
|
||||
_ = at.pool.Submit(func() {
|
||||
at.loadMux.Lock()
|
||||
_, loading := at.loading[tablePubkey]
|
||||
if loading {
|
||||
at.loadMux.Unlock()
|
||||
return
|
||||
}
|
||||
at.loading[tablePubkey] = struct{}{}
|
||||
at.loadMux.Unlock()
|
||||
|
||||
table, err := at.loadAddressTable(tablePubkey)
|
||||
if err != nil {
|
||||
logger.Error("loadAddressTable failed", "err", err, "table", tablePubkey)
|
||||
at.loadMux.Lock()
|
||||
delete(at.loading, tablePubkey)
|
||||
at.loadMux.Unlock()
|
||||
return
|
||||
}
|
||||
at.loadMux.Lock()
|
||||
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)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
at.mux.RUnlock()
|
||||
|
||||
var result solana.PublicKeySlice = make([]solana.PublicKey, 0, len(idx))
|
||||
for _, i := range idx {
|
||||
if int(i) >= len(addresses) {
|
||||
logger.Error("over loadAddressTable failed", "idx", i, "table", tablePubkey)
|
||||
//todo... update table?
|
||||
continue
|
||||
}
|
||||
result = append(result, addresses[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -2,35 +2,39 @@ package shreder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
log *slog.Logger
|
||||
|
||||
conn *grpc.ClientConn
|
||||
client ShrederServiceClient
|
||||
tableLoader *AddressTables
|
||||
subscription map[string]*SubscribeRequestFilterTransactions
|
||||
}
|
||||
|
||||
func NewShrederClient(
|
||||
url string,
|
||||
rpcClient *rpc.Client,
|
||||
subscription map[string]*SubscribeRequestFilterTransactions,
|
||||
) (*Client, func(), error) {
|
||||
if rpcClient == nil {
|
||||
return nil, func() {}, fmt.Errorf("rpc client is nil")
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(url, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return nil, func() {}, err
|
||||
}
|
||||
|
||||
logger := slog.Default()
|
||||
s := &Client{
|
||||
log: logger,
|
||||
conn: conn,
|
||||
client: NewShrederServiceClient(conn),
|
||||
subscription: subscription,
|
||||
tableLoader: NewAddressTables(rpcClient),
|
||||
}
|
||||
|
||||
return s, func() {
|
||||
@@ -39,14 +43,14 @@ func NewShrederClient(
|
||||
}
|
||||
|
||||
func (c *Client) Wait() {
|
||||
c.log.Debug("waiting for shreder client to stop")
|
||||
logger.Debug("waiting for shreder client to stop")
|
||||
|
||||
err := c.conn.Close()
|
||||
if err != nil {
|
||||
c.log.Error("failed to close connection: ", "err", err)
|
||||
logger.Error("failed to close connection: ", "err", err)
|
||||
}
|
||||
|
||||
c.log.Debug("shreder client stopped")
|
||||
logger.Debug("shreder client stopped")
|
||||
}
|
||||
|
||||
func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error {
|
||||
@@ -68,7 +72,7 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error
|
||||
return err
|
||||
}
|
||||
|
||||
txBatch := ParseTransaction(response.Transaction)
|
||||
txBatch := ParseTransaction(response.Transaction, c.tableLoader)
|
||||
if len(txBatch) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
2570
pkg/shreder/juptierv6-idl.json
Normal file
2570
pkg/shreder/juptierv6-idl.json
Normal file
File diff suppressed because it is too large
Load Diff
1068
pkg/shreder/juptierv6.go
Normal file
1068
pkg/shreder/juptierv6.go
Normal file
File diff suppressed because it is too large
Load Diff
88
pkg/shreder/juptierv6_test.go
Normal file
88
pkg/shreder/juptierv6_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
5
pkg/shreder/okxonchainlab.go
Normal file
5
pkg/shreder/okxonchainlab.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package shreder
|
||||
|
||||
//func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) {
|
||||
//
|
||||
//}
|
||||
@@ -1,6 +1,8 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -11,8 +13,23 @@ const (
|
||||
SolDecimals = 9
|
||||
)
|
||||
|
||||
var (
|
||||
logger *slog.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
|
||||
logger = slog.New(handler)
|
||||
}
|
||||
|
||||
func SetLogLevel(level slog.Level) {
|
||||
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})
|
||||
logger = slog.New(handler)
|
||||
}
|
||||
|
||||
type TxSignal struct {
|
||||
Source string `json:"source"`
|
||||
Label string `json:"label"`
|
||||
TxHash string `json:"tx_hash"`
|
||||
Maker string `json:"maker"`
|
||||
Token0Address string `json:"token0_address"`
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/mr-tron/base58"
|
||||
@@ -43,8 +43,23 @@ var (
|
||||
flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
|
||||
|
||||
terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
|
||||
|
||||
jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// instruction discriminators
|
||||
var (
|
||||
pumpCreateCoinIX = []byte{24, 30, 200, 40, 5, 28, 7, 119}
|
||||
@@ -81,11 +96,6 @@ var (
|
||||
terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1}
|
||||
)
|
||||
|
||||
// table lookups
|
||||
const (
|
||||
photonTableLookup = "3r6paeFSLpeUVmWtShb5uZtXYpcBE3729kUxkUS7xKi1"
|
||||
)
|
||||
|
||||
type compiledInstruction struct {
|
||||
ProgramIDIndex uint8
|
||||
Accounts []uint8
|
||||
@@ -183,7 +193,7 @@ type fjszBuyArgs struct {
|
||||
}
|
||||
|
||||
// ParseTransaction mirrors the Rust parse_transaction entry point.
|
||||
func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal {
|
||||
func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) []*TxSignal {
|
||||
versioned, err := toVersionedTransaction(update)
|
||||
if err != nil || versioned == nil || len(versioned.Signatures) == 0 {
|
||||
return nil
|
||||
@@ -193,6 +203,35 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal {
|
||||
staticKeys := versioned.Message.StaticAccountKeys
|
||||
instructions := versioned.Message.Instructions
|
||||
|
||||
if loader != nil && len(versioned.Message.AddressTableLookups) > 0 {
|
||||
lookupTableOk := true
|
||||
for _, lookup := range versioned.Message.AddressTableLookups {
|
||||
if len(lookup.WritableIndexes) == 0 {
|
||||
continue
|
||||
}
|
||||
accounts := loader.GetAddressTable(lookup.AccountKey, lookup.WritableIndexes)
|
||||
if len(accounts) != len(lookup.WritableIndexes) {
|
||||
lookupTableOk = false
|
||||
break
|
||||
}
|
||||
staticKeys = append(staticKeys, accounts...)
|
||||
|
||||
}
|
||||
if lookupTableOk {
|
||||
for _, lookup := range versioned.Message.AddressTableLookups {
|
||||
if len(lookup.ReadonlyIndexes) == 0 {
|
||||
continue
|
||||
}
|
||||
accounts := loader.GetAddressTable(lookup.AccountKey, lookup.ReadonlyIndexes)
|
||||
if len(accounts) != len(lookup.ReadonlyIndexes) {
|
||||
break
|
||||
}
|
||||
staticKeys = append(staticKeys, accounts...)
|
||||
}
|
||||
}
|
||||
versioned.Message.StaticAccountKeys = staticKeys
|
||||
}
|
||||
|
||||
var parsed []*TxSignal
|
||||
|
||||
for i := range instructions {
|
||||
@@ -233,6 +272,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal {
|
||||
case terminalProgramID:
|
||||
txRes, err := parseTermInstruction(versioned, i)
|
||||
parsed = appendParsed(parsed, txRes, err, txHash, "terminal", terminalProgramID.String())
|
||||
case jupiterV6ProgramID:
|
||||
txRes, err := parseJupiterV6Instruction(versioned, i)
|
||||
parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6", jupiterV6ProgramID.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +283,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal {
|
||||
|
||||
func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string, entryContract string) []*TxSignal {
|
||||
if err != nil {
|
||||
slog.Error("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:]))
|
||||
if !strings.HasPrefix(err.Error(), "account index") {
|
||||
logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:]))
|
||||
}
|
||||
return list
|
||||
}
|
||||
if parsed != nil {
|
||||
@@ -313,7 +357,7 @@ func formatSolAmount(lamports uint64) decimal.Decimal {
|
||||
|
||||
func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) {
|
||||
if index < 0 || index >= len(static) {
|
||||
return solana.PublicKey{}, fmt.Errorf("account index %d out of bounds", index)
|
||||
return solana.PublicKey{}, NewAccountNotFoundError(index, len(static))
|
||||
}
|
||||
return static[index], nil
|
||||
}
|
||||
@@ -359,6 +403,7 @@ func parsePumpCreate(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: creator.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -379,7 +424,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
if len(instruction.Data) < 8 {
|
||||
return nil, fmt.Errorf("data too short for create v2 args")
|
||||
return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
@@ -399,6 +444,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: args.Creator.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -416,7 +462,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
|
||||
|
||||
func decodePumpBuyArgs(data []byte) (uint64, uint64, error) {
|
||||
if len(data) < 9 {
|
||||
return 0, 0, fmt.Errorf("data too short for buy args")
|
||||
return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data))
|
||||
}
|
||||
|
||||
var args pumpBuyArgs
|
||||
@@ -462,6 +508,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -480,7 +527,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
|
||||
|
||||
func decodePumpSellArgs(data []byte) (uint64, uint64, error) {
|
||||
if len(data) < 9 {
|
||||
return 0, 0, fmt.Errorf("data too short for sell args")
|
||||
return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data))
|
||||
}
|
||||
|
||||
var args pumpExtendedSellArgs
|
||||
@@ -519,6 +566,7 @@ func parsePumpSell(tx *versionedTransaction, instruction *compiledInstruction) (
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pump",
|
||||
Maker: seller.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -570,13 +618,14 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal,
|
||||
}
|
||||
|
||||
if len(instruction.Data) < 17 {
|
||||
return nil, fmt.Errorf("data too short for buy args")
|
||||
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,
|
||||
@@ -610,7 +659,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
}
|
||||
|
||||
if len(instruction.Data) < 2 {
|
||||
return nil, fmt.Errorf("data too short for buy args")
|
||||
return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
var args azczBuyArgs
|
||||
@@ -620,6 +669,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "azcz",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -664,7 +714,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
}
|
||||
|
||||
if len(instruction.Data) < 2 {
|
||||
return nil, fmt.Errorf("data too short for buy args")
|
||||
return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
var args f5tfBuyArgs
|
||||
@@ -674,6 +724,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "f5tf",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -699,8 +750,11 @@ func parseFlasInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
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")
|
||||
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data))
|
||||
}
|
||||
methodData := instruction.Data[17:20]
|
||||
if !matchMethod(methodData, flasBuyTokensIX) {
|
||||
@@ -741,6 +795,7 @@ func parseFlasAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -780,6 +835,7 @@ func parseFlasAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal,
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -819,6 +875,7 @@ func parseFlasSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -861,6 +918,7 @@ func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "flas",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -906,7 +964,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
if len(instruction.Data) < 16 {
|
||||
return nil, fmt.Errorf("data too short for buy args")
|
||||
return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
@@ -927,6 +985,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
solAmount := args.SolAmount * (100000000 - 1234568) / 100000000
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "photon",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -947,7 +1006,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
if len(instruction.Data) < 16 {
|
||||
return nil, fmt.Errorf("data too short for swap args")
|
||||
return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
@@ -956,12 +1015,11 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quoteIndex := int(instruction.Accounts[4])
|
||||
quote, err := resolveQuoteAccount(tx, quoteIndex, []string{photonTableLookup}, 0)
|
||||
quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if quote != wsolMint {
|
||||
if !quote.Equals(solana.WrappedSol) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -983,6 +1041,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
solAmount := args.FromAmount * (100000000 - 1234568) / 100000000
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "photon",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1064,6 +1123,7 @@ func parseTermAmmSell(tx *versionedTransaction, instruction *compiledInstruction
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "term",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1099,6 +1159,7 @@ func parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "term",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1133,6 +1194,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "term",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1150,7 +1212,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (
|
||||
|
||||
func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) {
|
||||
if len(data) < 9 {
|
||||
return 0, 0, fmt.Errorf("data too short for buy args")
|
||||
return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data))
|
||||
}
|
||||
|
||||
var args pumpAmmBuyArgs
|
||||
@@ -1189,12 +1251,11 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quoteIndex := int(instruction.Accounts[4])
|
||||
quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0)
|
||||
quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if quote != wsolMint {
|
||||
if !quote.Equals(solana.WrappedSol) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -1205,6 +1266,7 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pumpamm",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1236,12 +1298,11 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quoteIndex := int(instruction.Accounts[4])
|
||||
quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0)
|
||||
quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if quote != wsolMint {
|
||||
if !quote.Equals(solana.WrappedSol) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -1252,6 +1313,7 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "pumpamm",
|
||||
Maker: buyer.String(),
|
||||
Token0Address: base.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1285,7 +1347,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
if len(instruction.Data) < 16 {
|
||||
return nil, fmt.Errorf("data too short for buy args")
|
||||
return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
@@ -1305,6 +1367,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "bobo",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1348,7 +1411,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
if len(instruction.Data) < 24 {
|
||||
return nil, fmt.Errorf("data too short for sell args")
|
||||
return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
@@ -1365,6 +1428,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e
|
||||
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,
|
||||
@@ -1386,7 +1450,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
if len(instruction.Data) < 24 {
|
||||
return nil, fmt.Errorf("data too short for sell args")
|
||||
return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
@@ -1403,6 +1467,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal
|
||||
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,
|
||||
@@ -1441,6 +1506,7 @@ func parseQtkvBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "qtkv",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1474,7 +1540,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
if len(instruction.Data) < 16 {
|
||||
return nil, fmt.Errorf("data too short for buy args")
|
||||
return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(instruction.Data))
|
||||
}
|
||||
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
@@ -1494,6 +1560,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
||||
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "fjsz",
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
@@ -1530,43 +1597,6 @@ func parseTerminalInstruction(tx *versionedTransaction, instructionIndex int) (*
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func resolveQuoteAccount(tx *versionedTransaction, quoteIndex int, expectedTableKeys []string, targetIndex uint8) (string, error) {
|
||||
staticKeys := tx.Message.StaticAccountKeys
|
||||
if quoteIndex < len(staticKeys) {
|
||||
quoteKey := staticKeys[quoteIndex].String()
|
||||
return quoteKey, nil
|
||||
}
|
||||
|
||||
// attempt to load from address table lookup
|
||||
if len(expectedTableKeys) == 0 || len(tx.Message.AddressTableLookups) != 1 {
|
||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
||||
}
|
||||
|
||||
table := tx.Message.AddressTableLookups[0]
|
||||
match := false
|
||||
for _, key := range expectedTableKeys {
|
||||
if table.AccountKey.String() == key {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
||||
}
|
||||
|
||||
indexOfTarget := indexOf(table.ReadonlyIndexes, targetIndex)
|
||||
if indexOfTarget < 0 {
|
||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
||||
}
|
||||
|
||||
expectedIndex := len(staticKeys) + len(table.WritableIndexes) + indexOfTarget
|
||||
if quoteIndex != expectedIndex {
|
||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
||||
}
|
||||
|
||||
return wsolMint, nil
|
||||
}
|
||||
|
||||
func indexOf(haystack []uint8, needle uint8) int {
|
||||
for i, v := range haystack {
|
||||
if v == needle {
|
||||
|
||||
Reference in New Issue
Block a user