Compare commits
44 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 |
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/ ./...
|
||||
@@ -25,6 +25,7 @@ func main() {
|
||||
}
|
||||
rpcClient := rpc.New(rpcUrl)
|
||||
shreder.SetLogLevel(slog.LevelDebug)
|
||||
//handlers := shreder.GetRegisteredHandlers()
|
||||
shrederClient, cleanup, err := shreder.NewShrederClient(
|
||||
url,
|
||||
rpcClient,
|
||||
@@ -55,13 +56,14 @@ func main() {
|
||||
"proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u",
|
||||
},
|
||||
},
|
||||
"dflow": {
|
||||
AccountRequired: []string{
|
||||
"DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH",
|
||||
},
|
||||
},
|
||||
|
||||
// TODO: axiom, gmgn, etc.
|
||||
}, shreder.BlocksStats(false), shreder.LogParsedStats(true), shreder.ShowTableLoaded(false))
|
||||
},
|
||||
//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)
|
||||
}
|
||||
@@ -91,8 +93,8 @@ func main() {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case tx := <-txCh:
|
||||
if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" {
|
||||
fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart))
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/gagliardetto/solana-go/programs/address-lookup-table"
|
||||
addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table"
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
|
||||
"github.com/samlior/libsam/v2/pkg/shreder"
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
const (
|
||||
rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
|
||||
txSignature = "3hFamox2W1oWMwbRkfF5r9YiPULsdRsnR2TQsFDVtFCXf6cJ8ijGNgHGFmEbxEbVEryLg21sbt4qoGLwrPfvJ2UC"
|
||||
txSignature = "4oSnHnDSscjmc6XX1rjXCEBavoLR9wkdZvGCAUn928iLWqrCwt2a6mgJpjP4NHqrCboUC82ugrjjEbNGNYAagkue"
|
||||
labelFilter = ""
|
||||
)
|
||||
|
||||
|
||||
@@ -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,10 +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,
|
||||
"soyas4s6L8KWZ8rsSk1mF3d1mQScoTGGAgjk98bF8nP": enum.SWQoSAgentSoyas,
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,5 @@ const (
|
||||
SWQoSAgentAstralane = "astralane"
|
||||
SWQoSAgentStellium = "stellium"
|
||||
SWQoSAgentSoyas = "soyas"
|
||||
SWQoSAgentFast = "fast"
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"google.golang.org/grpc"
|
||||
@@ -23,6 +24,10 @@ type Client struct {
|
||||
tableLoader *AddressTables
|
||||
subscription map[string]*SubscribeRequestFilterTransactions
|
||||
|
||||
entriesFilter map[string]FilterParams
|
||||
|
||||
parser map[solana.PublicKey]Handler
|
||||
|
||||
pool *ants.Pool
|
||||
|
||||
lastSlot uint64
|
||||
@@ -33,6 +38,8 @@ type ClientOpts struct {
|
||||
blockStats bool
|
||||
showTableLoaded bool
|
||||
logParseStats bool
|
||||
|
||||
parser map[solana.PublicKey]Handler
|
||||
}
|
||||
|
||||
type ClientOption func(*ClientOpts)
|
||||
@@ -43,6 +50,12 @@ func ShowTableLoaded(enable bool) ClientOption {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -82,14 +95,31 @@ func NewShrederClient(
|
||||
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,
|
||||
entriesFilter: filterParams,
|
||||
parser: o.parser,
|
||||
tableLoader: NewAddressTables(rpcClient, o.showTableLoaded),
|
||||
pool: pool,
|
||||
|
||||
@@ -142,7 +172,16 @@ func (c *Client) ReadEntriesSync(ctx context.Context, txCh chan<- TxSignal) erro
|
||||
}
|
||||
|
||||
err = c.pool.Submit(func() {
|
||||
ParseTransactionForEntries(ctx, slot, bytes.NewReader(response.Entries), c.tableLoader, txCh)
|
||||
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")
|
||||
@@ -187,10 +226,15 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
|
||||
}
|
||||
}
|
||||
|
||||
txData := response.Transaction
|
||||
// txData := response.Transaction
|
||||
|
||||
err := c.pool.Submit(func() {
|
||||
ParseTransactionForSubscribe(ctx, txData, c.tableLoader, txCh, nil)
|
||||
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")
|
||||
@@ -202,3 +246,11 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func parseAccountArray(accountArray []string) []solana.PublicKey {
|
||||
var result []solana.PublicKey
|
||||
for _, acc := range accountArray {
|
||||
result = append(result, solana.MustPublicKeyFromBase58(acc))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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
|
||||
@@ -26,11 +29,84 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int)
|
||||
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)
|
||||
@@ -41,8 +117,39 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int)
|
||||
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
|
||||
}
|
||||
@@ -61,38 +168,17 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
mint solana.PublicKey
|
||||
ok bool
|
||||
)
|
||||
for _, acctIdx := range instruction.Accounts {
|
||||
key, err := tx.GetAccount(int(acctIdx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.HasSuffix(key.String(), "pump") {
|
||||
mint = key
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Label: "bloomrouter",
|
||||
Maker: maker.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Address: base.String(),
|
||||
Token1Address: quote.String(),
|
||||
Token0Amount: formatTokenAmount(amount),
|
||||
Token1Amount: formatSolAmount(sol),
|
||||
Program: "Pump",
|
||||
Program: program,
|
||||
Event: event,
|
||||
ExactSOL: exactIn,
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: amount,
|
||||
Token1AmountUint64: sol,
|
||||
|
||||
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
|
||||
}
|
||||
@@ -72,6 +72,11 @@ const (
|
||||
ActOpenPredictionsOrder
|
||||
ActScorchSwap
|
||||
ActIncludeAccount
|
||||
|
||||
ActDFLOWStabbleWeightedSwap
|
||||
ActVertigoSwap
|
||||
ActSetMinimumLegOutputs
|
||||
ActSetMinimumLegPrices
|
||||
)
|
||||
|
||||
// DynamicRouteV1CandidateAction tags
|
||||
@@ -104,7 +109,7 @@ type dflowSwapParams struct {
|
||||
// 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:
|
||||
case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2, ActDFLOWStabbleWeightedSwap, ActVertigoSwap:
|
||||
// amount u64 + bool + orchestrator_flags u8
|
||||
return nil, dec.SkipBytes(8 + 1 + 1)
|
||||
case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap,
|
||||
@@ -181,6 +186,19 @@ func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,10 @@ func parseDlmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
|
||||
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
|
||||
@@ -220,6 +224,7 @@ func parseDlmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
|
||||
ExactSOL: exactSol,
|
||||
ActiveBin: args.ActiveBin,
|
||||
MaxPriceImpactBps: args.MaxPriceImpactBps,
|
||||
LbPairAddress: lbPair.String(),
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: token0AmountUint64,
|
||||
Token1AmountUint64: token1AmountUint64,
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
package shreder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/near/borsh-go"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
|
||||
var (
|
||||
flasBuyTokensIX = []byte{0x00, 0x1, 0x4}
|
||||
flasSellTokensIX = []byte{0x01, 0x1, 0x3}
|
||||
flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2}
|
||||
flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2}
|
||||
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 {
|
||||
@@ -22,6 +39,26 @@ type flasArgs struct {
|
||||
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")
|
||||
@@ -38,21 +75,22 @@ func parseFlasInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
|
||||
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data))
|
||||
}
|
||||
methodData := instruction.Data[17:20]
|
||||
//if !matchMethod(methodData, flasBuyTokensIX) {
|
||||
// return nil, nil
|
||||
//}
|
||||
var (
|
||||
err error
|
||||
txSignal *TxSignal
|
||||
)
|
||||
if matchMethod(methodData, flasBuyTokensIX) {
|
||||
if matchFlasMethod(methodData, flasBuyTokensIXs) {
|
||||
txSignal, err = parseFlasBuy(tx, instructionIndex)
|
||||
} else if matchMethod(methodData, flasSellTokensIX) {
|
||||
} else if matchFlasMethod(methodData, flasSellTokensIXs) {
|
||||
txSignal, err = parseFlasSell(tx, instructionIndex)
|
||||
} else if matchMethod(methodData, flasAmmBuyTokensIX) {
|
||||
} else if matchFlasMethod(methodData, flasAmmBuyTokensIXs) {
|
||||
txSignal, err = parseFlasAmmBuy(tx, instructionIndex)
|
||||
} else if matchMethod(methodData, flasAmmSellTokensIX) {
|
||||
} 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
|
||||
@@ -75,10 +113,16 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var args flasArgs
|
||||
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
||||
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(),
|
||||
@@ -86,7 +130,7 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal,
|
||||
Maker: user.String(),
|
||||
Token0Address: mint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token0Amount: formatTokenAmount(args.Amount1),
|
||||
Token0Amount: token0Amount,
|
||||
Token1Amount: formatSolAmount(args.Amount2),
|
||||
Program: "PumpAMM",
|
||||
Event: "sell",
|
||||
@@ -94,7 +138,7 @@ func parseFlasAmmSell(tx VersionedTransaction, instructionIndex int) (*TxSignal,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: args.Amount1,
|
||||
Token0AmountUint64: token0AmountUint64,
|
||||
Token1AmountUint64: args.Amount2,
|
||||
}, nil
|
||||
}
|
||||
@@ -114,8 +158,8 @@ func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var args flasArgs
|
||||
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
||||
}
|
||||
|
||||
@@ -140,11 +184,11 @@ func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal,
|
||||
|
||||
func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
||||
instruction := tx.Instructions[instructionIndex]
|
||||
if len(instruction.Accounts) < 9 {
|
||||
if len(instruction.Accounts) < 11 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
mint, err := tx.GetAccount(int(instruction.Accounts[8]))
|
||||
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -153,8 +197,8 @@ func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var args flasArgs
|
||||
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
||||
}
|
||||
|
||||
@@ -178,23 +222,20 @@ func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, er
|
||||
|
||||
func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
|
||||
instruction := tx.Instructions[instructionIndex]
|
||||
if len(instruction.Accounts) < 9 {
|
||||
if len(instruction.Accounts) < 11 {
|
||||
return nil, fmt.Errorf("accounts too short")
|
||||
}
|
||||
|
||||
mint, err := tx.GetAccount(int(instruction.Accounts[8]))
|
||||
mint, err := tx.GetAccount(int(instruction.Accounts[10]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := tx.GetAccount(int(instruction.Accounts[0]))
|
||||
user, err := tx.GetAccount(int(instruction.Accounts[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(instruction.Data) > 20 {
|
||||
instruction.Data = instruction.Data[:20]
|
||||
}
|
||||
var args flasArgs
|
||||
if err := borsh.Deserialize(&args, instruction.Data[1:]); err != nil {
|
||||
args, err := decodeFlasArgs(instruction.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse buy tokens args: %w", err)
|
||||
}
|
||||
|
||||
@@ -216,3 +257,94 @@ func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, err
|
||||
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
|
||||
}
|
||||
|
||||
@@ -157,6 +157,11 @@ const (
|
||||
PerenaStar
|
||||
JupiterRfqV2
|
||||
GoonFiV2
|
||||
Scorch
|
||||
VaultLiquidUnstake
|
||||
XOrca
|
||||
Quantum
|
||||
WhaleStreetV2
|
||||
)
|
||||
|
||||
var swapKindNames = [122]string{"Saber", "SaberAddDecimalsDeposit", "SaberAddDecimalsWithdraw", "TokenSwap", "Sencha", "Step", "Cropper",
|
||||
@@ -316,7 +321,7 @@ func decodeSwap(dec *bin.Decoder) (Swap, error) {
|
||||
BoopdotfunWrappedBuy, BoopdotfunWrappedSell, MeteoraDynamicBondingCurveSwapWithRemainingAccounts,
|
||||
PumpWrappedBuyV2, PumpWrappedSellV2, PumpSwapBuyV2, PumpSwapSellV2, Aquifer, PumpWrappedBuyV3, PumpWrappedSellV3,
|
||||
PumpSwapBuyV3, PumpSwapSellV3, JupiterLendDeposit, JupiterLendRedeem, RaydiumV2,
|
||||
MeteoraDammV2WithRemainingAccounts, Obsidian, PumpWrappedBuyV4, PumpWrappedSellV4, CarrotIssue, CarrotRedeem:
|
||||
MeteoraDammV2WithRemainingAccounts, Obsidian, PumpWrappedBuyV4, PumpWrappedSellV4, CarrotIssue, CarrotRedeem, XOrca:
|
||||
return out, nil
|
||||
|
||||
// -------- bool payload --------
|
||||
@@ -330,8 +335,20 @@ func decodeSwap(dec *bin.Decoder) (Swap, error) {
|
||||
case RaydiumLaunchlabBuy, RaydiumLaunchlabSell:
|
||||
return out, skipU64()
|
||||
// -------- Side(u8) payload --------
|
||||
case Serum, Aldrin, AldrinV2, Dradex, Openbook, Phoenix, OpenBookV2, TokenMill, Plasma, TesseraV, Futarchy, WhaleStreet, Manifest:
|
||||
case Serum, Aldrin, AldrinV2, Dradex, Openbook, Phoenix, OpenBookV2, TokenMill, Plasma, TesseraV, Futarchy,
|
||||
WhaleStreet, Manifest, Quantum:
|
||||
return out, skipU8()
|
||||
case WhaleStreetV2:
|
||||
if err := skipU8(); err != nil {
|
||||
return Swap{}, err
|
||||
}
|
||||
if err := skipU64(); err != nil {
|
||||
return Swap{}, err
|
||||
}
|
||||
if err := skipU64(); err != nil {
|
||||
return Swap{}, err
|
||||
}
|
||||
return out, nil
|
||||
// -------- MeteoraDlmmSwapV2: RemainingAccountsInfo --------
|
||||
case MeteoraDlmmSwapV2:
|
||||
return out, skipRemaining()
|
||||
@@ -406,9 +423,23 @@ func decodeSwap(dec *bin.Decoder) (Swap, error) {
|
||||
return Swap{}, err
|
||||
}
|
||||
return out, nil
|
||||
case Scorch:
|
||||
// side
|
||||
if err := skipTwoU64(); err != nil {
|
||||
return Swap{}, err
|
||||
}
|
||||
return out, nil
|
||||
case VaultLiquidUnstake:
|
||||
// [{"name":"lst_amounts","type":{"array":["u64",5]}},{"name":"seed","type":"u64"}]
|
||||
for i := 0; i <= 5; i++ {
|
||||
if err := skipU64(); err != nil {
|
||||
return Swap{}, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
default:
|
||||
// Unknown/new variant: assume no payload (keeps decoder aligned for RoutePlanStepV2 if it really is no-payload).
|
||||
return out, nil
|
||||
return out, fmt.Errorf("unknown Swap variant: %d", tag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,6 +1136,8 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
|
||||
isBuy bool
|
||||
isSell bool
|
||||
count int
|
||||
sellPercent uint8
|
||||
buyPercent uint8
|
||||
)
|
||||
for _, step := range plan {
|
||||
if !isInputIdx0(step.InputIdx) {
|
||||
@@ -1113,9 +1146,11 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
|
||||
if isPumpSwapSellKind(step.Swap.Kind) {
|
||||
isSell = true
|
||||
count++
|
||||
sellPercent = step.Percent
|
||||
} else if isPumpSwapBuyKind(step.Swap.Kind) {
|
||||
isBuy = true
|
||||
count++
|
||||
buyPercent = step.Percent
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
@@ -1137,6 +1172,9 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
|
||||
if in > 0 {
|
||||
token0Amount = formatTokenAmount(in)
|
||||
}
|
||||
if sellPercent > 0 && sellPercent < 100 {
|
||||
token0Amount = token0Amount.Mul(decimal.NewFromInt(int64(sellPercent))).Div(decimal.NewFromInt(100))
|
||||
}
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
@@ -1172,6 +1210,10 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
|
||||
if in > 0 {
|
||||
token1Amount = formatSolAmount(in)
|
||||
}
|
||||
if buyPercent > 0 && buyPercent < 100 {
|
||||
token1Amount = token1Amount.Mul(decimal.NewFromInt(int64(buyPercent))).Div(decimal.NewFromInt(100))
|
||||
token0Amount = token0Amount.Mul(decimal.NewFromInt(int64(buyPercent))).Div(decimal.NewFromInt(100))
|
||||
}
|
||||
return &TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
@@ -1211,6 +1253,38 @@ func findPumpFunMint(tx VersionedTransaction, accounts []uint8) (solana.PublicKe
|
||||
return solana.PublicKey{}, false, nil
|
||||
}
|
||||
|
||||
func findPreferredToken1Mint(tx VersionedTransaction, accounts []uint8) (solana.PublicKey, bool, error) {
|
||||
var stableMint solana.PublicKey
|
||||
sawSystemProgram := false
|
||||
|
||||
for _, acctIdx := range accounts {
|
||||
key, err := tx.GetAccount(int(acctIdx))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case key.Equals(solana.WrappedSol):
|
||||
return solana.WrappedSol, true, nil
|
||||
case isStableMint(key):
|
||||
if stableMint.IsZero() {
|
||||
stableMint = key
|
||||
} else if !stableMint.Equals(key) {
|
||||
return solana.PublicKey{}, false, nil
|
||||
}
|
||||
case key.Equals(solana.SystemProgramID):
|
||||
sawSystemProgram = true
|
||||
}
|
||||
}
|
||||
|
||||
if !stableMint.IsZero() {
|
||||
return stableMint, true, nil
|
||||
}
|
||||
if sawSystemProgram {
|
||||
return solana.WrappedSol, true, nil
|
||||
}
|
||||
return solana.PublicKey{}, false, nil
|
||||
}
|
||||
|
||||
func jupiterV6SourceDestMints(msg VersionedTransaction, instruction Instructions, disc []byte) (solana.PublicKey, solana.PublicKey, bool, error) {
|
||||
switch {
|
||||
case bytes.Equal(disc, jupiterRouteV2),
|
||||
@@ -1354,9 +1428,20 @@ func parseJupiterV6Instruction(tx VersionedTransaction, instructionIndex int) (T
|
||||
return nil, nil
|
||||
}
|
||||
if bytes.Equal(disc, jupiterRoute) {
|
||||
pumpMint, ok, err := findPumpFunMint(tx, instruction.Accounts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
if len(instruction.Accounts) < 13 {
|
||||
return nil, nil
|
||||
}
|
||||
token1Mint, token1MintOK, err := findPreferredToken1Mint(tx, instruction.Accounts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
destMint, err := tx.GetAccount(int(instruction.Accounts[5]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1369,21 +1454,30 @@ func parseJupiterV6Instruction(tx VersionedTransaction, instructionIndex int) (T
|
||||
if !pumpKey.Equals(pumpProgramID) {
|
||||
return nil, nil
|
||||
}
|
||||
token0Mint, err := tx.GetAccount(int(instruction.Accounts[12]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token0Amount := decimal.Zero
|
||||
if routeIn > 0 {
|
||||
token0Amount = formatTokenAmount(routeIn)
|
||||
}
|
||||
token1Amount := decimal.Zero
|
||||
token1Address := wsolMint
|
||||
if destMint.Equals(solana.WrappedSol) || destMint.Equals(solana.SystemProgramID) {
|
||||
token1Address = wsolMint
|
||||
if routeOut > 0 {
|
||||
token1Amount = formatSolAmount(routeOut)
|
||||
}
|
||||
} else {
|
||||
token1Address = destMint.String()
|
||||
if routeOut > 0 {
|
||||
token1Amount = formatTokenAmount(routeOut)
|
||||
}
|
||||
}
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: token0Mint.String(),
|
||||
Token1Address: destMint.String(),
|
||||
Token0Address: pumpMint.String(),
|
||||
Token1Address: token1Address,
|
||||
Token0Amount: token0Amount,
|
||||
Token1Amount: decimal.Zero,
|
||||
Token1Amount: token1Amount,
|
||||
Program: "Pump",
|
||||
Event: "sell",
|
||||
IsToken2022: false,
|
||||
@@ -1391,28 +1485,38 @@ func parseJupiterV6Instruction(tx VersionedTransaction, instructionIndex int) (T
|
||||
ExactSOL: false,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: routeIn,
|
||||
Token1AmountUint64: 0,
|
||||
Token1AmountUint64: routeOut,
|
||||
}}, nil
|
||||
}
|
||||
token0Amount := decimal.Zero
|
||||
if routeOut > 0 {
|
||||
token0Amount = formatTokenAmount(routeOut)
|
||||
}
|
||||
token1Amount := decimal.Zero
|
||||
token1Address := wsolMint
|
||||
if token1MintOK && isStableMint(token1Mint) {
|
||||
token1Address = token1Mint.String()
|
||||
if routeIn > 0 {
|
||||
token1Amount = formatTokenAmount(routeIn)
|
||||
}
|
||||
} else if routeIn > 0 {
|
||||
token1Amount = formatSolAmount(routeIn)
|
||||
}
|
||||
return TxSignalBatch{&TxSignal{
|
||||
TxHash: tx.Signatures[0].String(),
|
||||
Maker: tx.StaticAccountKeys[0].String(),
|
||||
Token0Address: destMint.String(),
|
||||
Token1Address: wsolMint,
|
||||
Token1Address: token1Address,
|
||||
Token0Amount: token0Amount,
|
||||
Token1Amount: decimal.Zero,
|
||||
Token1Amount: token1Amount,
|
||||
Program: "Pump",
|
||||
Event: "buy",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
ExactSOL: true,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: routeOut,
|
||||
Token1AmountUint64: 0,
|
||||
Token1AmountUint64: routeIn,
|
||||
}}, nil
|
||||
}
|
||||
if wrappedCnt > 1 {
|
||||
|
||||
@@ -3,6 +3,8 @@ package shreder
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
)
|
||||
|
||||
func TestDecodeRouteV2Arg(t *testing.T) {
|
||||
@@ -65,7 +67,11 @@ func TestDecodeRouteArg(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 1",
|
||||
hexData: "e517cb977ae3ad2a0200000028640001510000000000000000640102c09ee605000000005e1bc48efa000000d00700",
|
||||
hexData: "e517cb977ae3ad2a03000000646400017ab0b6c3d206f46577050000000c0000526401025f00640203bb628e2902000000338c430100000000320000",
|
||||
},
|
||||
{
|
||||
name: "Jupiter V6 RouteArg Test 2",
|
||||
hexData: "e517cb977ae3ad2a04000000642300024b00000000410002761acfb15ea9fdcd0501200204769358e96343759bf8014402046196591e1e020000f5bf6fe101000000d00700",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -86,3 +92,70 @@ func TestDecodeRouteArg(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -291,69 +291,189 @@ func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int
|
||||
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
|
||||
}
|
||||
var (
|
||||
inputAmount uint64
|
||||
routeCount int
|
||||
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 && (route.Dex == OKCV2_PumpfunammSell ||
|
||||
route.Dex == OKCV2_PumpfunSell2) {
|
||||
routeCount++
|
||||
inputAmount = args.AmountIn * uint64(route.Weight) / 10000
|
||||
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 routeCount > 1 {
|
||||
logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", routeCount)
|
||||
if pumpAmmSellCount > 1 {
|
||||
logger.Warn("pumpAmmSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpAmmSellCount)
|
||||
return nil, nil
|
||||
}
|
||||
if inputAmount == 0 {
|
||||
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
|
||||
}
|
||||
|
||||
srcMint, err := tx.GetAccount(int(ix.Accounts[3]))
|
||||
out := make(TxSignalBatch, 0, 2)
|
||||
|
||||
var (
|
||||
srcIdx uint8
|
||||
)
|
||||
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) {
|
||||
srcIdx = uint8(i + 6)
|
||||
pumpAmmIdx = uint8(i + 6)
|
||||
break
|
||||
}
|
||||
}
|
||||
if srcIdx == 0 || int(srcIdx+1) >= len(accounts) {
|
||||
if pumpAmmIdx == 0 || int(pumpAmmIdx+1) >= len(accounts) {
|
||||
if len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
baseMint, err := tx.GetAccount(int(accounts[srcIdx]))
|
||||
return out, nil
|
||||
}
|
||||
baseMint, err := tx.GetAccount(int(accounts[pumpAmmIdx]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !baseMint.Equals(srcMint) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
quoteMint, err := tx.GetAccount(int(accounts[srcIdx+1]))
|
||||
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 TxSignalBatch{&TxSignal{
|
||||
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: formatTokenAmount(inputAmount),
|
||||
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",
|
||||
@@ -361,7 +481,15 @@ func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
Token0AmountUint64: inputAmount,
|
||||
Token0AmountUint64: pumpAmmSellAmount,
|
||||
Token1AmountUint64: 0,
|
||||
}}, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(out) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -34,10 +34,18 @@ type pumpCreateCoinV2Args struct {
|
||||
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")
|
||||
@@ -114,8 +122,19 @@ func parsePumpCreateV2(tx VersionedTransaction, instruction Instructions) (*TxSi
|
||||
|
||||
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(),
|
||||
@@ -129,6 +148,7 @@ func parsePumpCreateV2(tx VersionedTransaction, instruction Instructions) (*TxSi
|
||||
Event: "create",
|
||||
IsToken2022: tokenProgramKey.String() != tokenProgram,
|
||||
IsMayhemMode: args.IsMayhemMode,
|
||||
IsCashbackEnabled: args.IsCashbackEnabled,
|
||||
Block: tx.Block,
|
||||
Token0AmountUint64: 0,
|
||||
Token1AmountUint64: 0,
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
var terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
|
||||
var (
|
||||
terminalBuyTokensIX = []byte{0xa6, 0x54, 0x14, 0x96, 0x9f, 0x77, 0x59, 0xca}
|
||||
terminalSellTokensIX = []byte{0xbe, 0x84, 0xa2, 0x96, 0x93, 0x7c, 0xf8, 0x6b}
|
||||
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}
|
||||
)
|
||||
|
||||
@@ -144,7 +144,7 @@ func parseTermSell(tx VersionedTransaction, instruction Instructions) (*TxSignal
|
||||
Token0Amount: formatTokenAmount(tokenAmount),
|
||||
Token1Amount: formatSolAmount(solAmount),
|
||||
Program: "Pump",
|
||||
Event: "buy",
|
||||
Event: "sell",
|
||||
IsToken2022: false,
|
||||
IsMayhemMode: false,
|
||||
ExactSOL: false,
|
||||
|
||||
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,7 +44,12 @@ 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"`
|
||||
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"`
|
||||
|
||||
@@ -53,6 +58,8 @@ type TxSignal struct {
|
||||
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:"-"`
|
||||
|
||||
@@ -3,6 +3,7 @@ package shreder
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/samlior/libsam/v2/pkg/consts"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
@@ -28,32 +30,39 @@ type FillAccount interface {
|
||||
}
|
||||
|
||||
func init() {
|
||||
for account := range parsedMap {
|
||||
parseProgram = append(parseProgram, account)
|
||||
for account := range registered {
|
||||
defaultFilterAccount = append(defaultFilterAccount, account)
|
||||
}
|
||||
//"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority
|
||||
//"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config
|
||||
//"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program
|
||||
parseProgram = append(parseProgram,
|
||||
defaultFilterAccount = append(defaultFilterAccount,
|
||||
solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"),
|
||||
solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"),
|
||||
solana.MustPublicKeyFromBase58("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
|
||||
)
|
||||
slices.SortFunc(parseProgram, func(a, b solana.PublicKey) int {
|
||||
slices.SortFunc(defaultFilterAccount, func(a, b solana.PublicKey) int {
|
||||
return bytes.Compare(a[:], b[:])
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
parseProgram []solana.PublicKey
|
||||
type FilterParams struct {
|
||||
Require []solana.PublicKey
|
||||
Include []solana.PublicKey
|
||||
Exclude []solana.PublicKey
|
||||
}
|
||||
|
||||
parsedMap = map[solana.PublicKey]Handler{
|
||||
var (
|
||||
defaultFilterAccount []solana.PublicKey
|
||||
|
||||
registered = map[solana.PublicKey]Handler{
|
||||
pumpProgramID: {parsePumpInstruction, "pump"},
|
||||
azczProgramID: {parseAzczInstruction, "azcz"},
|
||||
f5tfProgramID: {parseF5tfInstruction, "f5tf"},
|
||||
flasProgramID: {parseFlasInstruction, "flas"},
|
||||
photonProgramID: {parsePhotonInstruction, "photon"},
|
||||
pumpAmmProgramID: {parsePumpAmmInstruction, "pumpamm"},
|
||||
binanceWalletProgramID: {parseBinanceWalletInstruction, "binancewallet"},
|
||||
boboProgramID: {parseBoboInstruction, "bobo"},
|
||||
qtkvProgramID: {parseQtkvInstruction, "qtkv"},
|
||||
fjszProgramID: {parseFjszInstruction, "fjsz"},
|
||||
@@ -65,6 +74,10 @@ var (
|
||||
bonkProgramID: {parseBonkInstruction, "bonk"},
|
||||
bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"},
|
||||
dlmmProgramID: {parseDlmmInstruction, "dlmm"},
|
||||
dbotProgramID: {parseDbotInstruction, "dbot"},
|
||||
tradewizProgramID: {parseTradewizInstruction, "tradewiz"},
|
||||
maestroProgramId: {parseMaestroInstruction, "maestro"},
|
||||
raydiumLaunchLabProgramID: {parseRaydiumLaunchLabInstruction, "raydiumlaunchlab"},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -72,14 +85,21 @@ func ParseTransactionForSubscribe(ctx context.Context, update *SubscribeUpdateTr
|
||||
versioned, err := toVersionedTransaction(update)
|
||||
if err != nil {
|
||||
logger.Debug("txparser: failed to convert to versioned transaction", "error", err)
|
||||
if done != nil {
|
||||
close(done)
|
||||
}
|
||||
return
|
||||
}
|
||||
ParseTransaction(ctx, versioned, loader, parsed)
|
||||
if done != nil {
|
||||
close(done)
|
||||
}
|
||||
}
|
||||
|
||||
var VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
|
||||
var (
|
||||
ComputeBudgetProgram = solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111")
|
||||
VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
|
||||
)
|
||||
|
||||
func FilterTransactionForEntries(versioned VersionedTransaction) bool {
|
||||
if len(versioned.Instructions) >= 1 {
|
||||
@@ -92,7 +112,7 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
|
||||
// accounts filter?
|
||||
include := false
|
||||
for _, key := range versioned.StaticAccountKeys {
|
||||
_, include = slices.BinarySearchFunc(parseProgram, key, func(key solana.PublicKey, key2 solana.PublicKey) int {
|
||||
_, include = slices.BinarySearchFunc(defaultFilterAccount, key, func(key solana.PublicKey, key2 solana.PublicKey) int {
|
||||
return bytes.Compare(key[:], key2[:])
|
||||
})
|
||||
if include {
|
||||
@@ -102,6 +122,53 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
|
||||
return !include
|
||||
}
|
||||
|
||||
func GetRegisteredHandlers() map[solana.PublicKey]Handler {
|
||||
return registered
|
||||
}
|
||||
|
||||
func FilterTransactionForEntriesWithFilter(versioned VersionedTransaction, filter map[string]FilterParams) bool {
|
||||
if len(versioned.Instructions) >= 1 {
|
||||
programKey, _ := versioned.GetAccount(int(versioned.Instructions[0].ProgramIDIndex))
|
||||
if programKey.Equals(VoteProgram) && len(versioned.AddressTableLookups) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, params := range filter {
|
||||
excludePass := true
|
||||
// exclude first
|
||||
for _, key := range params.Exclude {
|
||||
if slices.Contains(versioned.StaticAccountKeys, key) {
|
||||
excludePass = false
|
||||
break
|
||||
}
|
||||
}
|
||||
requirePass := true
|
||||
if excludePass {
|
||||
for _, key := range params.Require {
|
||||
if !slices.Contains(versioned.StaticAccountKeys, key) {
|
||||
requirePass = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include := len(params.Include) == 0
|
||||
if excludePass && requirePass {
|
||||
for _, key := range params.Include {
|
||||
if slices.Contains(versioned.StaticAccountKeys, key) {
|
||||
include = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if excludePass && requirePass && include {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader io.Reader, loader *AddressTables, parsed chan<- TxSignal) {
|
||||
err := entriesToVersionedTransaction(slot, entriesReader, func(versioned VersionedTransaction) {
|
||||
// filter out vote transactions
|
||||
@@ -116,9 +183,9 @@ func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader
|
||||
}
|
||||
}
|
||||
|
||||
func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal) {
|
||||
// staticKeys := versioned.Message.StaticAccountKeys
|
||||
if loader != nil && len(versioned.AddressTableLookups) > 0 {
|
||||
func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal, handlers map[solana.PublicKey]Handler) {
|
||||
tableCnt := len(versioned.AddressTableLookups)
|
||||
if loader != nil && tableCnt > 0 {
|
||||
lookupTableOk := true
|
||||
for _, lookups := range versioned.AddressTableLookups {
|
||||
lookupTableOk = loader.FillToTx(&versioned, lookups.AccountKey, lookups.WritableIndexes)
|
||||
@@ -136,13 +203,51 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
|
||||
}
|
||||
}
|
||||
|
||||
cuPrice := decimal.Zero
|
||||
swqosAgent := ""
|
||||
swqosTips := decimal.Zero
|
||||
cuLimit := uint32(0)
|
||||
for _, instruction := range versioned.Instructions {
|
||||
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if program.Equals(ComputeBudgetProgram) {
|
||||
if len(instruction.Data) == 9 && instruction.Data[0] == 0x03 {
|
||||
cuPriceUint64 := binary.LittleEndian.Uint64(instruction.Data[1:9])
|
||||
cuPrice = formatCUPrice(cuPriceUint64)
|
||||
} else if len(instruction.Data) == 5 && instruction.Data[0] == 0x02 {
|
||||
cuLimit = binary.LittleEndian.Uint32(instruction.Data[1:5])
|
||||
}
|
||||
}
|
||||
if program.Equals(solana.SystemProgramID) &&
|
||||
len(instruction.Data) == 12 &&
|
||||
instruction.Data[0] == 0x02 &&
|
||||
instruction.Data[1] == 0x00 &&
|
||||
instruction.Data[2] == 0x00 &&
|
||||
instruction.Data[3] == 0x00 &&
|
||||
len(instruction.Accounts) >= 2 {
|
||||
destination, err := versioned.GetAccount(int(instruction.Accounts[1]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var ok bool
|
||||
swqosAgent, ok = consts.SWQoSFeeAddresses2Pubkeys[destination]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
swqosTipsUint64 := binary.LittleEndian.Uint64(instruction.Data[4:12])
|
||||
swqosTips = formatSolAmount(swqosTipsUint64)
|
||||
}
|
||||
}
|
||||
|
||||
for i, instruction := range versioned.Instructions {
|
||||
//load from address table
|
||||
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
handler, ok := parsedMap[program]
|
||||
handler, ok := handlers[program]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -160,6 +265,11 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
|
||||
}
|
||||
one.Label = handler.Label
|
||||
one.Block = versioned.Block
|
||||
one.CUPrice = cuPrice
|
||||
one.CULimit = cuLimit
|
||||
one.SWQoSAgent = swqosAgent
|
||||
one.SWQoSTips = swqosTips
|
||||
one.TableCnt = tableCnt
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
@@ -173,6 +283,11 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
|
||||
return
|
||||
}
|
||||
|
||||
func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal) {
|
||||
// staticKeys := versioned.Message.StaticAccountKeys
|
||||
ParseTransactionWithHandler(ctx, versioned, loader, parsed, registered)
|
||||
}
|
||||
|
||||
func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransaction, error) {
|
||||
if update == nil || update.Transaction == nil || update.Transaction.Message == nil {
|
||||
return VersionedTransaction{}, fmt.Errorf("transaction is nil")
|
||||
@@ -224,6 +339,11 @@ func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransa
|
||||
return versioned, nil
|
||||
}
|
||||
|
||||
func formatCUPrice(cuPrice uint64) decimal.Decimal {
|
||||
val := decimal.NewFromBigInt(new(big.Int).SetUint64(cuPrice), 0)
|
||||
return val.Div(decimal.NewFromInt(1_000_000))
|
||||
}
|
||||
|
||||
func formatTokenAmount(amount uint64) decimal.Decimal {
|
||||
val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0)
|
||||
return val.Div(decimal.NewFromInt(1_000_000))
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/gagliardetto/solana-go"
|
||||
"github.com/gagliardetto/solana-go/rpc"
|
||||
"github.com/near/borsh-go"
|
||||
)
|
||||
|
||||
func TestDecodeAxiomArgs(t *testing.T) {
|
||||
@@ -49,8 +48,8 @@ func TestDecodeAxiomArgs(t *testing.T) {
|
||||
t.Fatalf("failed to decode hex string: %v", err)
|
||||
return
|
||||
}
|
||||
var args flasArgs
|
||||
if err := borsh.Deserialize(&args, data[1:]); err != nil {
|
||||
args, err := decodeFlasArgs(data)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode Axiom args: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -59,6 +58,41 @@ func TestDecodeAxiomArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeAxiomUpdatedRouteMarkers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
marker []byte
|
||||
match func([]byte) bool
|
||||
}{
|
||||
{name: "pump buy 0021", marker: []byte{0x00, 0x01, 0x21}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBuyTokensIXs) }},
|
||||
{name: "pump buy 001b", marker: []byte{0x00, 0x01, 0x1b}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBuyTokensIXs) }},
|
||||
{name: "pump sell", marker: []byte{0x01, 0x01, 0x1a}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasSellTokensIXs) }},
|
||||
{name: "pump amm buy", marker: []byte{0x00, 0x02, 0x1f}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasAmmBuyTokensIXs) }},
|
||||
{name: "pump amm sell", marker: []byte{0x01, 0x02, 0x1f}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasAmmSellTokensIXs) }},
|
||||
{name: "legacy bonk buy", marker: []byte{0x00, 0x02, 0x07}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBonkBuyTokensIXs) }},
|
||||
{name: "legacy bonk sell", marker: []byte{0x01, 0x02, 0x07}, match: func(marker []byte) bool { return matchFlasMethod(marker, flasBonkSellTokensIXs) }},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
data := make([]byte, 20)
|
||||
data[0] = 0
|
||||
copy(data[17:20], tt.marker)
|
||||
args, err := decodeFlasArgs(data)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode args: %v", err)
|
||||
}
|
||||
expected := [3]uint8{tt.marker[0], tt.marker[1], tt.marker[2]}
|
||||
if args.Placeholder != expected {
|
||||
t.Fatalf("expected marker %x, got %x", tt.marker, args.Placeholder)
|
||||
}
|
||||
if !tt.match(tt.marker) {
|
||||
t.Fatalf("marker %x did not match route", tt.marker)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func toUpdata(slot uint64, tx *solana.Transaction) *SubscribeUpdateTransaction {
|
||||
signatures := make([][]byte, len(tx.Signatures))
|
||||
for i, sig := range tx.Signatures {
|
||||
@@ -154,12 +188,16 @@ func TestParseTermBuy(t *testing.T) {
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "5Gz1fa4Qhb35bkg9QCMXpxCX5uuNr7WcjcmrwajGZA7kXsvNS9pDnYe12ggWeSqf1nwZbVPob6DkX6fcwbE9ofBR"),
|
||||
getTransaction(t, client, "4rm1UFvWqTrBvcCfEzeXEPCeNsXRNhMHRx7AXrEiBwpFMJXNzXPt9zhrpQc1JrUnuBACeo7uRM8W8vKx56TQT7vs"),
|
||||
nil,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
@@ -175,17 +213,68 @@ func TestParseTermBuy(t *testing.T) {
|
||||
if signal.Event != "buy" {
|
||||
t.Fatalf("expected buy event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "BaLxyjXzATAnfm7cc5AFhWBpiwnsb71THcnofDLTWAPK" {
|
||||
t.Fatalf("expected maker BaLxyjXzATAnfm7cc5AFhWBpiwnsb71THcnofDLTWAPK, got %s", signal.Maker)
|
||||
if signal.Maker != "ATEruR96FhZWpZGkKvgzGG3gRL3Cdj6GttHtBsUFeAPE" {
|
||||
t.Fatalf("expected maker ATEruR96FhZWpZGkKvgzGG3gRL3Cdj6GttHtBsUFeAPE, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "5Wgv54peXRKDHYHapAELzgNKEPEh9E5Bf3hUR3sTpump" {
|
||||
t.Fatalf("expected token0 address 5Wgv54peXRKDHYHapAELzgNKEPEh9E5Bf3hUR3sTpump, got %s", signal.Token0Address)
|
||||
if signal.Token0Address != "7nHYtqhR4qq7LHVmC5Pnz7p6Se64i39TfnyM6ZC2pump" {
|
||||
t.Fatalf("expected token0 address 7nHYtqhR4qq7LHVmC5Pnz7p6Se64i39TfnyM6ZC2pump, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 6952026214256 {
|
||||
if signal.Token0AmountUint64 != 4473828190746 {
|
||||
t.Fatalf("expected token0 amount 6952026214256, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 653333333 {
|
||||
t.Fatalf("expected token1 amount 653333333, got %d", signal.Token1AmountUint64)
|
||||
if signal.Token1AmountUint64 != 250000000 {
|
||||
t.Fatalf("expected token1 amount 250000000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTermSell(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "4oSnHnDSscjmc6XX1rjXCEBavoLR9wkdZvGCAUn928iLWqrCwt2a6mgJpjP4NHqrCboUC82ugrjjEbNGNYAagkue"),
|
||||
nil,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "terminal" {
|
||||
t.Fatalf("expected terminal signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "sell" {
|
||||
t.Fatalf("expected sell event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "DjAj23BSiaRJKJziKHwtLexEHRDgL4tEjx4Ye4Gkug2E" {
|
||||
t.Fatalf("expected maker DjAj23BSiaRJKJziKHwtLexEHRDgL4tEjx4Ye4Gkug2E, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "5J6TKpfP5SHDaUNAdDg23c31wS3KnfZ3JTfFErt4pump" {
|
||||
t.Fatalf("expected token0 address 5J6TKpfP5SHDaUNAdDg23c31wS3KnfZ3JTfFErt4pump, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 6384819151429 {
|
||||
t.Fatalf("expected token0 amount 6384819151429, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 243633622 {
|
||||
t.Fatalf("expected token1 amount 243633622, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +297,10 @@ func TestParseBonkBuy(t *testing.T) {
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
@@ -256,6 +349,10 @@ func TestParseBonkSell(t *testing.T) {
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
@@ -305,6 +402,10 @@ func TestParsePhotonBuy(t *testing.T) {
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
@@ -353,6 +454,10 @@ func TestParseJupiterV6PumpFunBuy(t *testing.T) {
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
@@ -404,6 +509,10 @@ func TestParseJupiterV6PumpFunSell(t *testing.T) {
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
@@ -435,3 +544,780 @@ func TestParseJupiterV6PumpFunSell(t *testing.T) {
|
||||
t.Fatalf("expected ExactSOL false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRaydiumLaunchLabBuyExactIn(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "2erxUsE92LdrxhWy6HryUJpvBpVUociu2UY6AGoX7E6orrqm6AYxDzhmub3J9PDPa5CPNwWZBG8rCxKCdquVo2Lc"),
|
||||
nil,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "raydiumlaunchlab" {
|
||||
t.Fatalf("expected raydiumlaunchlab signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "buy" {
|
||||
t.Fatalf("expected buy event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "56JZV81H3XJedVWcV8RTXrn5YD6WW2k2LTGVDGCUwyYb" {
|
||||
t.Fatalf("expected maker 56JZV81H3XJedVWcV8RTXrn5YD6WW2k2LTGVDGCUwyYb, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "676zr3qFwy3XUXwVkQVdV9cidSaxcS6SrHga8cK4kKej" {
|
||||
t.Fatalf("expected token0 address 676zr3qFwy3XUXwVkQVdV9cidSaxcS6SrHga8cK4kKej, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 15336821188103 {
|
||||
t.Fatalf("expected token0 amount 15336821188103, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 1000000000 {
|
||||
t.Fatalf("expected token1 amount 1000000000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if !signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRaydiumLaunchLabBuyExactOut(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "4x2gArQAezap4RZKK5Tpfu1SNadCsTbjeUYG5KNggoHeGFiZdV1MmrrvXLXvyh2e6C1Gh22ohU4dFAzGw18y6VLT"),
|
||||
nil,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "raydiumlaunchlab" {
|
||||
t.Fatalf("expected raydiumlaunchlab signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "buy" {
|
||||
t.Fatalf("expected buy event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "nya666pQkP3PzWxi7JngU3rRMHuc7zbLK8c8wxQ4qpT" {
|
||||
t.Fatalf("expected maker nya666pQkP3PzWxi7JngU3rRMHuc7zbLK8c8wxQ4qpT, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "8ZTAvKJiPqExQYTYXoE2FuCoRFJAmyuEXXb4GbPvbonk" {
|
||||
t.Fatalf("expected token0 address 8ZTAvKJiPqExQYTYXoE2FuCoRFJAmyuEXXb4GbPvbonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 2888057167672 {
|
||||
t.Fatalf("expected token0 amount 2888057167672, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 159300000 {
|
||||
t.Fatalf("expected token1 amount 159300000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRaydiumLaunchLabSellExactIn(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("4xBW5fcrR1eityayfv6mrxC7zfQNy89YpqoVmqiTbfRh"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "37LGuUiTnBpagELdwVWtffkbJaLsucbRimnFueKS23EaFdYsjHC42SS3XjyC5tyRaokj5tdwiKYfBqNFAmMbTUgv"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "raydiumlaunchlab" {
|
||||
t.Fatalf("expected raydiumlaunchlab signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "sell" {
|
||||
t.Fatalf("expected sell event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "7GSDLZHBNnEgYwe8TLLy8iDbnKWaR5NfNVoxvQJ26nux" {
|
||||
t.Fatalf("expected maker 7GSDLZHBNnEgYwe8TLLy8iDbnKWaR5NfNVoxvQJ26nux, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "6bs2ECcL1XG5Tw8t3xvpyCsQDunQpdYtDXNyD74bonk" {
|
||||
t.Fatalf("expected token0 address 6bs2ECcL1XG5Tw8t3xvpyCsQDunQpdYtDXNyD74bonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 3144872514758 {
|
||||
t.Fatalf("expected token0 amount 3144872514758, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 373068521 {
|
||||
t.Fatalf("expected token1 amount 373068521, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRaydiumLaunchLabSellExactOut(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("DSaHkhDp17UexbZsg2VUnWjEuTwKNCJrnG4LW122ANfd"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "3z8iC9zDLeQzjcLtUnwDT1j9Z5p5rFWXyCcRUhvUCVDTrPJSoZbbULkMTZk2mUUeAX1qaMBZUsPCd59B4KaGooSk"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "raydiumlaunchlab" {
|
||||
t.Fatalf("expected raydiumlaunchlab signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "sell" {
|
||||
t.Fatalf("expected sell event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "F4RMpjLMJZKmrYBxjpVadty7e326XUziLkYTDtNtpo2e" {
|
||||
t.Fatalf("expected maker F4RMpjLMJZKmrYBxjpVadty7e326XUziLkYTDtNtpo2e, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "ByubRkVymfNBoX9DUfUL39Zi6qzqWkTbLviQtx9ktxWv" {
|
||||
t.Fatalf("expected token0 address ByubRkVymfNBoX9DUfUL39Zi6qzqWkTbLviQtx9ktxWv, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 594669717336 {
|
||||
t.Fatalf("expected token0 amount 594669717336, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 141400000 {
|
||||
t.Fatalf("expected token1 amount 141400000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if !signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRaydiumLaunchLabCreate(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("J7aYNVkReYKUSEvS79sg2YubtMQMxUetByAFrNw7qZ3G"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "2qi4STgj33b1DydMKvtpqeSVwkcTJHww8ViX9ADMu2TaRz2uSGcVjgDyd9AELnn2N1ojSGy2qnM6uiXJc2vEcLmw"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) == 0 {
|
||||
t.Fatalf("expected at least 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "raydiumlaunchlab" {
|
||||
t.Fatalf("expected raydiumlaunchlab signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "create" {
|
||||
t.Fatalf("expected create event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "F9TNxWymThfvs4kVVmyaBqx8pjxpvP7kScNBqiczeY84" {
|
||||
t.Fatalf("expected maker F9TNxWymThfvs4kVVmyaBqx8pjxpvP7kScNBqiczeY84, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "5VvwdKsYUjPTNbEotwDcV48PQEfi42of7TzaLyaybonk" {
|
||||
t.Fatalf("expected token0 address 5VvwdKsYUjPTNbEotwDcV48PQEfi42of7TzaLyaybonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.IsToken2022 {
|
||||
t.Fatalf("expected IsToken2022 false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRaydiumLaunchLabCreate2(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("5Sq6FRoQkRbupnZM1iq9AFeLaSZPbVHgPheVqHD4GzVr"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "3ZofgvJ3vrNRnyGfAFwENSrDAHxVuXhqrL4svwctHuxZHnAengj5SHeEwPJiJFPXYaBcnfrUAk2V4368LVVSEP2W"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) == 0 {
|
||||
t.Fatalf("expected at least 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "raydiumlaunchlab" {
|
||||
t.Fatalf("expected raydiumlaunchlab signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "create" {
|
||||
t.Fatalf("expected create event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "7GhWwhaMgbKiRWxF93Bud6HnHMci6NCLTJyTxG8zFH51" {
|
||||
t.Fatalf("expected maker 7GhWwhaMgbKiRWxF93Bud6HnHMci6NCLTJyTxG8zFH51, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "GWeFeDVD75PVGHdLf7HrYJe2BYTiA8J3nVJXSqb4CpoU" {
|
||||
t.Fatalf("expected token0 address GWeFeDVD75PVGHdLf7HrYJe2BYTiA8J3nVJXSqb4CpoU, got %s", signal.Token0Address)
|
||||
}
|
||||
if !signal.IsToken2022 {
|
||||
t.Fatalf("expected IsToken2022 true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func manuallyLoadAddressTable(t *testing.T, client *rpc.Client, tablePubkey solana.PublicKey) *AddressTables {
|
||||
loader := NewAddressTables(client, false)
|
||||
table, err := loader.loadAddressTable(tablePubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load address table: %s", err)
|
||||
}
|
||||
|
||||
loader.tables.Add(tablePubkey, &TableInfo{
|
||||
addresses: table,
|
||||
})
|
||||
|
||||
return loader
|
||||
}
|
||||
|
||||
func TestParsePumpCreate(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("AXVvmhWaaPtV52jqYuTNqp1xRrkbxhfJfeHQKxq5cbvZ"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "3vUAHpBUoxeoZheo9m3XmufNUWmJWRAN4xZjSqDos71GL6tCKSTmJV6YeMS5XdVAbRAfqQi1rPusjbmEhoam4x9Y"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) == 0 {
|
||||
t.Fatalf("expected at least 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "pump" {
|
||||
t.Fatalf("expected pump signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "create" {
|
||||
t.Fatalf("expected create event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "8oVJLE69qH1i5jotFABcCoaPS38DAyZ7djRg6uuD3Cb7" {
|
||||
t.Fatalf("expected maker 8oVJLE69qH1i5jotFABcCoaPS38DAyZ7djRg6uuD3Cb7, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "AtaWsdyfcANLsHNdpYJyFdNvwSQnUzvhRN2MCCP9pump" {
|
||||
t.Fatalf("expected token0 address AtaWsdyfcANLsHNdpYJyFdNvwSQnUzvhRN2MCCP9pump, got %s", signal.Token0Address)
|
||||
}
|
||||
if !signal.IsToken2022 {
|
||||
t.Fatalf("expected IsToken2022 true, got false")
|
||||
}
|
||||
if signal.IsCashbackEnabled {
|
||||
t.Fatalf("expected IsCashbackEnabled true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePumpCreate2(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "3EKKjtNFzhtQaA9GyPt5UJHLr5mWT2XodaxWoenUrcaPpN8BKm84ATVapdUJcb9sJVFyS4iKD9spGBKfqkSFutea"),
|
||||
nil,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) == 0 {
|
||||
t.Fatalf("expected at least 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "pump" {
|
||||
t.Fatalf("expected pump signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "create" {
|
||||
t.Fatalf("expected create event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "5mGqXMqUbJzxQ9aqbBttB4JUfAMqceSTmrtpt6RuPXdC" {
|
||||
t.Fatalf("expected maker 5mGqXMqUbJzxQ9aqbBttB4JUfAMqceSTmrtpt6RuPXdC, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "J9Hqi5VddcTNJ2F5EyxSZxC2JpJeymfJXjLhsCVPZpna" {
|
||||
t.Fatalf("expected token0 address J9Hqi5VddcTNJ2F5EyxSZxC2JpJeymfJXjLhsCVPZpna, got %s", signal.Token0Address)
|
||||
}
|
||||
if !signal.IsToken2022 {
|
||||
t.Fatalf("expected IsToken2022 true, got false")
|
||||
}
|
||||
if signal.IsCashbackEnabled {
|
||||
t.Fatalf("expected IsCashbackEnabled true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFlasBonkBuy(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("7RKtfATWCe98ChuwecNq8XCzAzfoK3DtZTprFsPMGtio"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "5ED51fnabzxsPqjswp7R9qbfuTep7avtsQnsYg4R6w2jc9Ys2mMCXFNNnDNvUUhaREJS5Tz1dSfBL1dufXzDsiaX"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "flas" {
|
||||
t.Fatalf("expected flas signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "buy" {
|
||||
t.Fatalf("expected buy event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "75KjigN4rgweGMRu5oWY4DBPELpQ1TYsBQAXuzs7hKVA" {
|
||||
t.Fatalf("expected maker 75KjigN4rgweGMRu5oWY4DBPELpQ1TYsBQAXuzs7hKVA, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk" {
|
||||
t.Fatalf("expected token0 address 2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 1052495896871 {
|
||||
t.Fatalf("expected token0 amount 1052495896871, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 99000000 {
|
||||
t.Fatalf("expected token1 amount 99000000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if !signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFlasBonkSell(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("7RKtfATWCe98ChuwecNq8XCzAzfoK3DtZTprFsPMGtio"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "2v3qLsnrJ5KqUDqtzXyc3S9vT6cLvXbaVR6vwfhp4ufC4Sg1vmR5xMdxzrtvErq8kiC8g7d5wLAbEMe8NwXJE5MS"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "flas" {
|
||||
t.Fatalf("expected flas signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "sell" {
|
||||
t.Fatalf("expected sell event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "7rtyqW2yr76Y9iCTvbAzkDdaJU8mbx3ZuzW9sTZ3pV2q" {
|
||||
t.Fatalf("expected maker 7rtyqW2yr76Y9iCTvbAzkDdaJU8mbx3ZuzW9sTZ3pV2q, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk" {
|
||||
t.Fatalf("expected token0 address 2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 6413676607028 {
|
||||
t.Fatalf("expected token0 amount 6413676607028, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 249361301 {
|
||||
t.Fatalf("expected token1 amount 249361301, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMaestroBonkBuy(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("HPjn8EFrUcMxqQrdRzJkgXp85cGUHLFVkY4nZ7EUBbir"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "LuGGhtCU5enHY2J8qt5KAZybvQyokxKn4NxkhQfKz6RbkxW1anU9vHfAXeEVsjM49mtPmeyyVKKW1myaAXt6BhJ"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "maestro" {
|
||||
t.Fatalf("expected maestro signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "buy" {
|
||||
t.Fatalf("expected buy event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "Gyz2QP89RuoFG55V2xVpyGiG7rMya41j1ZbFNbd1WfF9" {
|
||||
t.Fatalf("expected maker Gyz2QP89RuoFG55V2xVpyGiG7rMya41j1ZbFNbd1WfF9, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk" {
|
||||
t.Fatalf("expected token0 address 2aMTnF7Kz9aRhTMmiVuSwbQ9Msrdkgm4RvFoJfZPbonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 11089351947578 {
|
||||
t.Fatalf("expected token0 amount 1052495896871, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 1000000000 {
|
||||
t.Fatalf("expected token1 amount 1000000000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if !signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMaestroBonkSell(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("HPjn8EFrUcMxqQrdRzJkgXp85cGUHLFVkY4nZ7EUBbir"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "3cXpA8C5uizp1iK8fV8eoxoonT5BcT7G52wN9aRsRwi9pUCUyuDt2FwXVVtbkvocxoAD82ZQWjCaLRgswgcszTHQ"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "maestro" {
|
||||
t.Fatalf("expected maestro signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "sell" {
|
||||
t.Fatalf("expected sell event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "fwBVjnPQHwrAVWP4VnwbA1Y9BiDWnixJ39G5PA59sZv" {
|
||||
t.Fatalf("expected maker fwBVjnPQHwrAVWP4VnwbA1Y9BiDWnixJ39G5PA59sZv, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "9553NoaZEQGYsttrym5w85RQVHhwVi3BfNU9GLfEbonk" {
|
||||
t.Fatalf("expected token0 address 9553NoaZEQGYsttrym5w85RQVHhwVi3BfNU9GLfEbonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 11299453877090 {
|
||||
t.Fatalf("expected token0 amount 11299453877090, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 892516080 {
|
||||
t.Fatalf("expected token1 amount 892516080, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBloomRouterBonkBuy(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("3xRUDpys1Yy96y5QVBfhW94ukphuDXHsKh4mTAFofA6S"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "5XEGgHXokNKQpgUUf1zS8LXFRHR7XNBaPiRZxGumFkBH23b3TsTjs6wJ1NRHxf6xRvYBLwXWWJdw7AiNzAAgUzgg"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "bloomrouter" {
|
||||
t.Fatalf("expected bloomrouter signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "buy" {
|
||||
t.Fatalf("expected buy event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "9ozVSeSsgASFDUjveLNQV697kAavStvpXmKP65oMt2Ji" {
|
||||
t.Fatalf("expected maker 9ozVSeSsgASFDUjveLNQV697kAavStvpXmKP65oMt2Ji, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "6qzh89yisR498GsQysqzj69AW9BJc39LWhXtzxudbonk" {
|
||||
t.Fatalf("expected token0 address 6qzh89yisR498GsQysqzj69AW9BJc39LWhXtzxudbonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 0 {
|
||||
t.Fatalf("expected token0 amount 0, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 1500000000 {
|
||||
t.Fatalf("expected token1 amount 1500000000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if !signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBloomRouterBonkSell(t *testing.T) {
|
||||
rpcUrl := os.Getenv("SOL_RPC_URL")
|
||||
if rpcUrl == "" {
|
||||
t.Fatalf("SOL_RPC_URL is not set")
|
||||
}
|
||||
|
||||
client := rpc.New(rpcUrl)
|
||||
loader := manuallyLoadAddressTable(t, client, solana.MustPublicKeyFromBase58("3xRUDpys1Yy96y5QVBfhW94ukphuDXHsKh4mTAFofA6S"))
|
||||
ch := make(chan TxSignal)
|
||||
closed := make(chan struct{})
|
||||
go func() {
|
||||
ParseTransactionForSubscribe(
|
||||
context.Background(),
|
||||
getTransaction(t, client, "MMdN29CYKDKDurbhUHn51AyyJ5ZEZq1F1TFJTrVYhRT1moaURqXAHJb2pFus4KrXAdAFo5Hr1Jw4bgE2EWeLXf6"),
|
||||
loader,
|
||||
ch,
|
||||
closed,
|
||||
)
|
||||
}()
|
||||
go func() {
|
||||
<-closed
|
||||
close(ch)
|
||||
}()
|
||||
signals := make([]TxSignal, 0)
|
||||
for signal := range ch {
|
||||
signals = append(signals, signal)
|
||||
}
|
||||
|
||||
if len(signals) != 1 {
|
||||
t.Fatalf("expected 1 signal, got %d", len(signals))
|
||||
}
|
||||
|
||||
signal := signals[0]
|
||||
if signal.Label != "bloomrouter" {
|
||||
t.Fatalf("expected bloomrouter signal, got %s", signal.Label)
|
||||
}
|
||||
if signal.Event != "sell" {
|
||||
t.Fatalf("expected sell event, got %s", signal.Event)
|
||||
}
|
||||
if signal.Maker != "HWiPtESw8pvhQWm1a7vbHg9rsnee13Pmb7dUGVC3f4mZ" {
|
||||
t.Fatalf("expected maker HWiPtESw8pvhQWm1a7vbHg9rsnee13Pmb7dUGVC3f4mZ, got %s", signal.Maker)
|
||||
}
|
||||
if signal.Token0Address != "6Yd5AGP4E4b1prHxJmuHexUToohiNKiQSG2nXvXWbonk" {
|
||||
t.Fatalf("expected token0 address 6Yd5AGP4E4b1prHxJmuHexUToohiNKiQSG2nXvXWbonk, got %s", signal.Token0Address)
|
||||
}
|
||||
if signal.Token0AmountUint64 != 6448480270053 {
|
||||
t.Fatalf("expected token0 amount 6448480270053, got %d", signal.Token0AmountUint64)
|
||||
}
|
||||
if signal.Token1AmountUint64 != 1000 {
|
||||
t.Fatalf("expected token1 amount 1000, got %d", signal.Token1AmountUint64)
|
||||
}
|
||||
if signal.ExactSOL {
|
||||
t.Fatalf("expected ExactSOL false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user