3 Commits

Author SHA1 Message Date
thloyi
8c98ec7875 cache address table 2026-01-05 14:38:02 +08:00
thloyi
e6922e4561 loading address tables 2026-01-05 12:45:32 +08:00
4afa412231 chore: add swqos fee addrsses 2025-12-30 16:52:41 +08:00
8 changed files with 330 additions and 86 deletions

View File

@@ -2,13 +2,15 @@ package main
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/gagliardetto/solana-go/rpc"
"github.com/samlior/libsam/pkg/shreder" "github.com/samlior/libsam/pkg/shreder"
) )
@@ -17,13 +19,24 @@ func main() {
if url == "" { if url == "" {
panic("URL is not set") panic("URL is not set")
} }
rpcUrl := os.Getenv("RPC_URL")
if rpcUrl == "" {
panic("RPC_URL is not set")
}
rpcClient := rpc.New(rpcUrl)
shreder.SetLogLevel(slog.LevelDebug)
shrederClient, cleanup, err := shreder.NewShrederClient( shrederClient, cleanup, err := shreder.NewShrederClient(
url, url,
rpcClient,
map[string]*shreder.SubscribeRequestFilterTransactions{ map[string]*shreder.SubscribeRequestFilterTransactions{
"pumpfunamm": { "pumpfunamm": {
AccountRequired: []string{ //AccountRequired: []string{
// "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
//},
AccountInclude: []string{
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority
"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config
}, },
}, },
"photon": { "photon": {
@@ -63,8 +76,8 @@ func main() {
case <-ctx.Done(): case <-ctx.Done():
return return
case txBatch := <-txCh: case txBatch := <-txCh:
jsonData, _ := json.MarshalIndent(txBatch, "", " ") //jsonData, _ := json.MarshalIndent(txBatch, "", " ")
fmt.Println(string(jsonData)) fmt.Println(txBatch[0].TxHash)
} }
} }
} }

5
go.mod
View File

@@ -5,7 +5,9 @@ go 1.25.1
require ( require (
github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250722092120-44561cb37455 github.com/BlockRazorinc/solana-trader-client-go v0.0.0-20250722092120-44561cb37455
github.com/gagliardetto/solana-go v1.12.0 github.com/gagliardetto/solana-go v1.12.0
github.com/mr-tron/base58 v1.2.0
github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454 github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454
github.com/panjf2000/ants/v2 v2.11.4
github.com/shopspring/decimal v1.4.0 github.com/shopspring/decimal v1.4.0
google.golang.org/grpc v1.75.0 google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.10 google.golang.org/protobuf v1.36.10
@@ -20,6 +22,7 @@ require (
github.com/gagliardetto/binary v0.8.0 // indirect github.com/gagliardetto/binary v0.8.0 // indirect
github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect github.com/klauspost/compress v1.13.6 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
@@ -29,7 +32,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect
go.mongodb.org/mongo-driver v1.12.2 // indirect go.mongodb.org/mongo-driver v1.12.2 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
@@ -38,6 +40,7 @@ require (
go.uber.org/zap v1.21.0 // indirect go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.44.0 // indirect golang.org/x/crypto v0.44.0 // indirect
golang.org/x/net v0.47.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/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect golang.org/x/text v0.31.0 // indirect

9
go.sum
View File

@@ -36,6 +36,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -68,6 +70,8 @@ github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454 h1:lFN7TVecCMbCHVN
github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454/go.mod h1:NeMochZp7jN/pYFuxLkrZtmLqbADmnp/y1+/dL+AsyQ= github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454/go.mod h1:NeMochZp7jN/pYFuxLkrZtmLqbADmnp/y1+/dL+AsyQ=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -81,8 +85,9 @@ github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:Vl
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -140,6 +145,8 @@ golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -0,0 +1,124 @@
package consts
import "github.com/samlior/libsam/pkg/enum"
var SWQoSFeeAddresses = map[string]string{
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5": enum.SWQoSAgentJito,
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe": enum.SWQoSAgentJito,
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY": enum.SWQoSAgentJito,
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49": enum.SWQoSAgentJito,
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh": enum.SWQoSAgentJito,
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt": enum.SWQoSAgentJito,
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL": enum.SWQoSAgentJito,
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT": enum.SWQoSAgentJito,
"6fQaVhYZA4w3MBSXjJ81Vf6W1EDYeUPXpgVQ6UQyU1Av": enum.SWQoSAgent0slot,
"4HiwLEP2Bzqj3hM2ENxJuzhcPCdsafwiet3oGkMkuQY4": enum.SWQoSAgent0slot,
"7toBU3inhmrARGngC7z6SjyP85HgGMmCTEwGNRAcYnEK": enum.SWQoSAgent0slot,
"8mR3wB1nh4D6J9RUCugxUpc6ya8w38LPxZ3ZjcBhgzws": enum.SWQoSAgent0slot,
"6SiVU5WEwqfFapRuYCndomztEwDjvS5xgtEof3PLEGm9": enum.SWQoSAgent0slot,
"TpdxgNJBWZRL8UXF5mrEsyWxDWx9HQexA9P1eTWQ42p": enum.SWQoSAgent0slot,
"D8f3WkQu6dCF33cZxuAsrKHrGsqGP2yvAHf8mX6RXnwf": enum.SWQoSAgent0slot,
"GQPFicsy3P3NXxB5piJohoxACqTvWE9fKpLgdsMduoHE": enum.SWQoSAgent0slot,
"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,
"FogxVNs6Mm2w9rnGL1vkARSwJxvLE8mujTv3LK8RnUhF": enum.SWQoSAgentBlocxRoute,
"3UQUKjhMKaY2S6bjcQD6yHB7utcZt5bfarRCmctpRtUd": enum.SWQoSAgentBlocxRoute,
"TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq": enum.SWQoSAgentNozomi,
"noz3jAjPiHuBPqiSPkkugaJDkJscPuRhYnSpbi8UvC4": enum.SWQoSAgentNozomi,
"noz3str9KXfpKknefHji8L1mPgimezaiUyCHYMDv1GE": enum.SWQoSAgentNozomi,
"noz6uoYCDijhu1V7cutCpwxNiSovEwLdRHPwmgCGDNo": enum.SWQoSAgentNozomi,
"noz9EPNcT7WH6Sou3sr3GGjHQYVkN3DNirpbvDkv9YJ": enum.SWQoSAgentNozomi,
"nozc5yT15LazbLTFVZzoNZCwjh3yUtW86LoUyqsBu4L": enum.SWQoSAgentNozomi,
"nozFrhfnNGoyqwVuwPAW4aaGqempx4PU6g6D9CJMv7Z": enum.SWQoSAgentNozomi,
"nozievPk7HyK1Rqy1MPJwVQ7qQg2QoJGyP71oeDwbsu": enum.SWQoSAgentNozomi,
"noznbgwYnBLDHu8wcQVCEw6kDrXkPdKkydGJGNXGvL7": enum.SWQoSAgentNozomi,
"nozNVWs5N8mgzuD3qigrCG2UoKxZttxzZ85pvAQVrbP": enum.SWQoSAgentNozomi,
"nozpEGbwx4BcGp6pvEdAh1JoC2CQGZdU6HbNP1v2p6P": enum.SWQoSAgentNozomi,
"nozrhjhkCr3zXT3BiT4WCodYCUFeQvcdUkM7MqhKqge": enum.SWQoSAgentNozomi,
"nozrwQtWhEdrA6W8dkbt9gnUaMs52PdAv5byipnadq3": enum.SWQoSAgentNozomi,
"nozUacTVWub3cL4mJmGCYjKZTnE9RbdY5AP46iQgbPJ": enum.SWQoSAgentNozomi,
"nozWCyTPppJjRuw2fpzDhhWbW355fzosWSzrrMYB1Qk": enum.SWQoSAgentNozomi,
"nozWNju6dY353eMkMqURqwQEoM3SFgEKC6psLCSfUne": enum.SWQoSAgentNozomi,
"nozxNBgWohjR75vdspfxR5H9ceC7XXH99xpxhVGt3Bb": enum.SWQoSAgentNozomi,
"NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE": enum.SWQoSAgentNextBlock,
"NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2": enum.SWQoSAgentNextBlock,
"NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X": enum.SWQoSAgentNextBlock,
"NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb": enum.SWQoSAgentNextBlock,
"neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At": enum.SWQoSAgentNextBlock,
"nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG": enum.SWQoSAgentNextBlock,
"NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid": enum.SWQoSAgentNextBlock,
"nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc": enum.SWQoSAgentNextBlock,
"4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE": enum.SWQoSAgentHelius,
"D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ": enum.SWQoSAgentHelius,
"9bnz4RShgq1hAnLnZbP8kbgBg1kEmcJBYQq3gQbmnSta": enum.SWQoSAgentHelius,
"5VY91ws6B2hMmBFRsXkoAAdsPHBJwRfBht4DXox3xkwn": enum.SWQoSAgentHelius,
"2nyhqdwKcJZR2vcqCyrYsaPVdAnFoJjiksCXJ7hfEYgD": enum.SWQoSAgentHelius,
"2q5pghRs6arqVjRvT5gfgWfWcHWmw1ZuCzphgd5KfWGJ": enum.SWQoSAgentHelius,
"wyvPkWjVZz1M8fHQnMMCDTQDbkManefNNhweYk5WkcF": enum.SWQoSAgentHelius,
"3KCKozbAaF75qEU33jtzozcJ29yJuaLJTy2jFdzUY8bT": enum.SWQoSAgentHelius,
"4vieeGHPYPG2MmyPRcYjdiDmmhN3ww7hsFNap8pVN3Ey": enum.SWQoSAgentHelius,
"4TQLFNWK8AovT1gFvda5jfw2oJeRMKEmw7aH6MGBJ3or": enum.SWQoSAgentHelius,
"node1PqAa3BWWzUnTHVbw8NJHC874zn9ngAkXjgWEej": enum.SWQoSAgentNode1,
"node1UzzTxAAeBTpfZkQPJXBAqixsbdth11ba1NXLBG": enum.SWQoSAgentNode1,
"node1Qm1bV4fwYnCurP8otJ9s5yrkPq7SPZ5uhj3Tsv": enum.SWQoSAgentNode1,
"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,
"FLASHRzANfcAKDuQ3RXv9hbkBy4WVEKDzoAgxJ56DiE4": enum.SWQoSAgentFlashBlock,
"FLAsHZTRcf3Dy1APaz6j74ebdMC6Xx4g6i9YxjyrDybR": enum.SWQoSAgentFlashBlock,
"FLAshyAyBcKb39KPxSzXcepiS8iDYUhDGwJcJDPX4g2B": enum.SWQoSAgentFlashBlock,
"FLaSHJNm5dWYzEgnHJWWJP5ccu128Mu61NJLxUf7mUXU": enum.SWQoSAgentFlashBlock,
"FLaSHR4Vv7sttd6TyDF4yR1bJyAxRwWKbohDytEMu3wL": enum.SWQoSAgentFlashBlock,
"FLaShB3iXXTWE1vu9wQsChUKq3HFtpMAhb8kAh1pf1wi": enum.SWQoSAgentFlashBlock,
"FLAShWTjcweNT4NSotpjpxAkwxUr2we3eXQGhpTVzRwy": enum.SWQoSAgentFlashBlock,
"FjmZZrFvhnqqb9ThCuMVnENaM3JGVuGWNyCAxRJcFpg9": enum.SWQoSAgentBlockRazor,
"6No2i3aawzHsjtThw81iq1EXPJN6rh8eSJCLaYZfKDTG": enum.SWQoSAgentBlockRazor,
"A9cWowVAiHe9pJfKAj3TJiN9VpbzMUq6E4kEvf5mUT22": enum.SWQoSAgentBlockRazor,
"Gywj98ophM7GmkDdaWs4isqZnDdFCW7B46TXmKfvyqSm": enum.SWQoSAgentBlockRazor,
"68Pwb4jS7eZATjDfhmTXgRJjCiZmw1L7Huy4HNpnxJ3o": enum.SWQoSAgentBlockRazor,
"4ABhJh5rZPjv63RBJBuyWzBK3g9gWMUQdTZP2kiW31V9": enum.SWQoSAgentBlockRazor,
"B2M4NG5eyZp5SBQrSdtemzk5TqVuaWGQnowGaCBt8GyM": enum.SWQoSAgentBlockRazor,
"5jA59cXMKQqZAVdtopv8q3yyw9SYfiE3vUCbt7p8MfVf": enum.SWQoSAgentBlockRazor,
"5YktoWygr1Bp9wiS1xtMtUki1PeYuuzuCF98tqwYxf61": enum.SWQoSAgentBlockRazor,
"295Avbam4qGShBYK7E9H5Ldew4B3WyJGmgmXfiWdeeyV": enum.SWQoSAgentBlockRazor,
"EDi4rSy2LZgKJX74mbLTFk4mxoTgT6F7HxxzG2HBAFyK": enum.SWQoSAgentBlockRazor,
"BnGKHAC386n4Qmv9xtpBVbRaUTKixjBe3oagkPFKtoy6": enum.SWQoSAgentBlockRazor,
"Dd7K2Fp7AtoN8xCghKDRmyqr5U169t48Tw5fEd3wT9mq": enum.SWQoSAgentBlockRazor,
"AP6qExwrbRgBAVaehg4b5xHENX815sMabtBzUzVB4v8S": enum.SWQoSAgentBlockRazor,
"astrazznxsGUhWShqgNtAdfrzP2G83DzcWVJDxwV9bF": enum.SWQoSAgentAstralane,
"astra4uejePWneqNaJKuFFA8oonqCE1sqF6b45kDMZm": enum.SWQoSAgentAstralane,
"astra9xWY93QyfG6yM8zwsKsRodscjQ2uU2HKNL5prk": enum.SWQoSAgentAstralane,
"astraRVUuTHjpwEVvNBeQEgwYx9w9CFyfxjYoobCZhL": enum.SWQoSAgentAstralane,
"astraEJ2fEj8Xmy6KLG7B3VfbKfsHXhHrNdCQx7iGJK": enum.SWQoSAgentAstralane,
"astraubkDw81n4LuutzSQ8uzHCv4BhPVhfvTcYv8SKC": enum.SWQoSAgentAstralane,
"astraZW5GLFefxNPAatceHhYjfA1ciq9gvfEg2S47xk": enum.SWQoSAgentAstralane,
"astrawVNP4xDBKT7rAdxrLYiTSTdqtUr63fSMduivXK": enum.SWQoSAgentAstralane,
"ste11JV3MLMM7x7EJUM2sXcJC1H7F4jBLnP9a9PG8PH": enum.SWQoSAgentStellium,
"ste11MWPjXCRfQryCshzi86SGhuXjF4Lv6xMXD2AoSt": enum.SWQoSAgentStellium,
"ste11p5x8tJ53H1NbNQsRBg1YNRd4GcVpxtDw8PBpmb": enum.SWQoSAgentStellium,
"ste11p7e2KLYou5bwtt35H7BM6uMdo4pvioGjJXKFcN": enum.SWQoSAgentStellium,
"ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX": enum.SWQoSAgentStellium,
}

View File

@@ -0,0 +1,101 @@
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
tables *lru.Cache[solana.PublicKey, []solana.PublicKey]
loading map[solana.PublicKey]struct{}
pool *ants.Pool
}
func NewAddressTables(rpcClient *rpc.Client) *AddressTables {
pool, _ := ants.NewPool(10, 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.mux.RLock()
_, loading := at.loading[tablePubkey]
if loading {
at.mux.RUnlock()
return
}
at.mux.RUnlock()
at.mux.Lock()
at.loading[tablePubkey] = struct{}{}
at.mux.Unlock()
table, err := at.loadAddressTable(tablePubkey)
if err != nil {
logger.Error("loadAddressTable failed", "err", err, "table", tablePubkey)
at.mux.Lock()
delete(at.loading, tablePubkey)
at.mux.Unlock()
return
}
at.mux.Lock()
at.tables.Add(tablePubkey, table)
total := at.tables.Len()
delete(at.loading, tablePubkey)
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(idx[i]) >= len(addresses) {
continue
}
result = append(result, addresses[i])
}
return result
}

View File

@@ -2,35 +2,39 @@ package shreder
import ( import (
"context" "context"
"log/slog" "fmt"
"github.com/gagliardetto/solana-go/rpc"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
) )
type Client struct { type Client struct {
log *slog.Logger
conn *grpc.ClientConn conn *grpc.ClientConn
client ShrederServiceClient client ShrederServiceClient
tableLoader *AddressTables
subscription map[string]*SubscribeRequestFilterTransactions subscription map[string]*SubscribeRequestFilterTransactions
} }
func NewShrederClient( func NewShrederClient(
url string, url string,
rpcClient *rpc.Client,
subscription map[string]*SubscribeRequestFilterTransactions, subscription map[string]*SubscribeRequestFilterTransactions,
) (*Client, func(), error) { ) (*Client, func(), error) {
if rpcClient == nil {
return nil, func() {}, fmt.Errorf("rpc client is nil")
}
conn, err := grpc.NewClient(url, grpc.WithTransportCredentials(insecure.NewCredentials())) conn, err := grpc.NewClient(url, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil { if err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
logger := slog.Default()
s := &Client{ s := &Client{
log: logger,
conn: conn, conn: conn,
client: NewShrederServiceClient(conn), client: NewShrederServiceClient(conn),
subscription: subscription, subscription: subscription,
tableLoader: NewAddressTables(rpcClient),
} }
return s, func() { return s, func() {
@@ -39,14 +43,14 @@ func NewShrederClient(
} }
func (c *Client) Wait() { 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() err := c.conn.Close()
if err != nil { 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 { 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 return err
} }
txBatch := ParseTransaction(response.Transaction) txBatch := ParseTransaction(response.Transaction, c.tableLoader)
if len(txBatch) == 0 { if len(txBatch) == 0 {
continue continue
} }

View File

@@ -1,6 +1,8 @@
package shreder package shreder
import ( import (
"log/slog"
"os"
"time" "time"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
@@ -11,6 +13,20 @@ const (
SolDecimals = 9 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 { type TxSignal struct {
Source string `json:"source"` Source string `json:"source"`
TxHash string `json:"tx_hash"` TxHash string `json:"tx_hash"`

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"log/slog"
"math/big" "math/big"
"github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go"
@@ -81,11 +80,6 @@ var (
terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1} terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1}
) )
// table lookups
const (
photonTableLookup = "3r6paeFSLpeUVmWtShb5uZtXYpcBE3729kUxkUS7xKi1"
)
type compiledInstruction struct { type compiledInstruction struct {
ProgramIDIndex uint8 ProgramIDIndex uint8
Accounts []uint8 Accounts []uint8
@@ -193,7 +187,7 @@ type fjszBuyArgs struct {
} }
// ParseTransaction mirrors the Rust parse_transaction entry point. // ParseTransaction mirrors the Rust parse_transaction entry point.
func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal { func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) []*TxSignal {
versioned, err := toVersionedTransaction(update) versioned, err := toVersionedTransaction(update)
if err != nil || versioned == nil || len(versioned.Signatures) == 0 { if err != nil || versioned == nil || len(versioned.Signatures) == 0 {
return nil return nil
@@ -203,6 +197,22 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal {
staticKeys := versioned.Message.StaticAccountKeys staticKeys := versioned.Message.StaticAccountKeys
instructions := versioned.Message.Instructions instructions := versioned.Message.Instructions
if loader != nil && len(versioned.Message.AddressTableLookups) > 0 {
// currently we only care about photon table lookup
for _, lookup := range versioned.Message.AddressTableLookups {
accounts := loader.GetAddressTable(lookup.AccountKey, lookup.WritableIndexes)
if len(accounts) != len(lookup.WritableIndexes) {
break
}
staticKeys = append(staticKeys, accounts...)
accounts2 := loader.GetAddressTable(lookup.AccountKey, lookup.ReadonlyIndexes)
if len(accounts2) != len(lookup.ReadonlyIndexes) {
break
}
staticKeys = append(staticKeys, accounts2...)
}
}
var parsed []*TxSignal var parsed []*TxSignal
for i := range instructions { for i := range instructions {
@@ -251,7 +261,7 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal {
func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal { func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal {
if err != nil { if err != nil {
slog.Error("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:]))
return list return list
} }
if parsed != nil { if parsed != nil {
@@ -321,8 +331,11 @@ func formatSolAmount(lamports uint64) decimal.Decimal {
} }
func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) { func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) {
if index < 0 || index >= len(static) { if index < 0 {
return solana.PublicKey{}, fmt.Errorf("account index %d out of bounds", index) return solana.PublicKey{}, fmt.Errorf("account index %d less then 0", index)
}
if index >= len(static) {
return solana.PublicKey{}, fmt.Errorf("account index %d out of range", index)
} }
return static[index], nil return static[index], nil
} }
@@ -388,7 +401,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
if len(instruction.Data) < 8 { 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 staticKeys := tx.Message.StaticAccountKeys
@@ -425,7 +438,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
func decodePumpBuyArgs(data []byte) (uint64, uint64, error) { func decodePumpBuyArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 { 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 var args pumpBuyArgs
@@ -489,7 +502,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
func decodePumpSellArgs(data []byte) (uint64, uint64, error) { func decodePumpSellArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 { 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 var args pumpExtendedSellArgs
@@ -579,7 +592,7 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal,
} }
if len(instruction.Data) < 17 { 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]) solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9])
@@ -619,7 +632,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
} }
if len(instruction.Data) < 2 { 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 var args azczBuyArgs
@@ -673,7 +686,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
} }
if len(instruction.Data) < 2 { 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 var args f5tfBuyArgs
@@ -708,8 +721,11 @@ func parseFlasInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
if len(instruction.Data) == 0 { if len(instruction.Data) == 0 {
return nil, fmt.Errorf("data is empty") return nil, fmt.Errorf("data is empty")
} }
if len(instruction.Data) == 10 && instruction.Data[0] == 1 {
return nil, nil
}
if len(instruction.Data) < 20 { 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] methodData := instruction.Data[17:20]
if !matchMethod(methodData, flasBuyTokensIX) { if !matchMethod(methodData, flasBuyTokensIX) {
@@ -911,7 +927,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction)
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
if len(instruction.Data) < 16 { 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 staticKeys := tx.Message.StaticAccountKeys
@@ -952,7 +968,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
if len(instruction.Data) < 16 { 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 staticKeys := tx.Message.StaticAccountKeys
@@ -961,12 +977,11 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
return nil, err return nil, err
} }
quoteIndex := int(instruction.Accounts[4]) quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
quote, err := resolveQuoteAccount(tx, quoteIndex, []string{photonTableLookup}, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if quote != wsolMint { if !quote.Equals(solana.WrappedSol) {
return nil, nil return nil, nil
} }
@@ -1155,7 +1170,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (
func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) { func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) {
if len(data) < 9 { 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 var args pumpAmmBuyArgs
@@ -1194,12 +1209,11 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction)
if err != nil { if err != nil {
return nil, err return nil, err
} }
quoteIndex := int(instruction.Accounts[4]) quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if quote != wsolMint { if !quote.Equals(solana.WrappedSol) {
return nil, nil return nil, nil
} }
@@ -1241,12 +1255,11 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction
if err != nil { if err != nil {
return nil, err return nil, err
} }
quoteIndex := int(instruction.Accounts[4]) quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if quote != wsolMint { if !quote.Equals(solana.WrappedSol) {
return nil, nil return nil, nil
} }
@@ -1290,7 +1303,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
if len(instruction.Data) < 16 { 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 staticKeys := tx.Message.StaticAccountKeys
@@ -1353,7 +1366,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
if len(instruction.Data) < 24 { 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 staticKeys := tx.Message.StaticAccountKeys
@@ -1391,7 +1404,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
if len(instruction.Data) < 24 { 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 staticKeys := tx.Message.StaticAccountKeys
@@ -1479,7 +1492,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
return nil, fmt.Errorf("accounts too short") return nil, fmt.Errorf("accounts too short")
} }
if len(instruction.Data) < 16 { 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 staticKeys := tx.Message.StaticAccountKeys
@@ -1535,43 +1548,6 @@ func parseTerminalInstruction(tx *versionedTransaction, instructionIndex int) (*
return nil, nil 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 { func indexOf(haystack []uint8, needle uint8) int {
for i, v := range haystack { for i, v := range haystack {
if v == needle { if v == needle {