19 Commits

Author SHA1 Message Date
cachalots
5d06d18aa8 culimit 2026-02-27 15:32:36 +08:00
9877794d1c fix: flas buy/sell 2026-02-21 19:03:00 +08:00
f6242f0193 Merge branch 'master' of https://go.onsig.ai/onsig/libsam 2026-02-21 18:52:17 +08:00
b06a1fa377 chore: upgrade axiom pumpfun buy/sell 2026-02-21 18:42:18 +08:00
75c35f56f1 fix juo v6 pump swap percent 2026-02-17 19:13:12 +08:00
79859bc079 update dflow new action 2026-02-15 20:41:47 +08:00
bijianing97
bd2dbe3c91 Add lbPairAddress for TxSignal 2026-02-12 17:37:29 +08:00
thloyi
22d2df3782 fix juptierv6 swap kind 2026-02-12 12:44:48 +08:00
eb75bebbfd Merge branch 'master' of https://github.com/samlior/libsam 2026-02-05 14:32:31 +08:00
db9e2b33cb chore: add parse swqos logic 2026-02-05 14:32:24 +08:00
bijianing97
f41e086028 Fix bloomrouter parser 2026-02-05 14:13:40 +08:00
77c8c0aad3 chore: add cu price 2026-02-03 17:36:59 +08:00
bijianing97
a0e46ec83e Add tradewiz parser 2026-02-03 14:06:43 +08:00
bijianing97
3324a71117 Update okxonchainlab parser 2026-02-02 15:39:05 +08:00
bijianing97
7557414fff Add Dbot parser 2026-02-02 11:02:10 +08:00
thloyi
eb394c5650 entries custom filter and parse 2026-01-30 12:13:31 +08:00
thloyi
1223b34117 make buidl linux-x86 2026-01-29 16:44:16 +08:00
bijianing97
d866701679 Add binanceWallet pumpfun 2026-01-29 16:40:32 +08:00
fa1875996c fix another sfng bug 2026-01-28 18:42:34 +08:00
19 changed files with 1113 additions and 112 deletions

View File

@@ -19,4 +19,4 @@ shreder:
.PHONY: build .PHONY: build
# build # 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/ ./...

View File

@@ -25,6 +25,7 @@ func main() {
} }
rpcClient := rpc.New(rpcUrl) rpcClient := rpc.New(rpcUrl)
shreder.SetLogLevel(slog.LevelDebug) shreder.SetLogLevel(slog.LevelDebug)
//handlers := shreder.GetRegisteredHandlers()
shrederClient, cleanup, err := shreder.NewShrederClient( shrederClient, cleanup, err := shreder.NewShrederClient(
url, url,
rpcClient, rpcClient,
@@ -55,13 +56,14 @@ func main() {
"proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u", "proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u",
}, },
}, },
"dflow": {
AccountRequired: []string{
"DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH",
},
},
// TODO: axiom, gmgn, etc. // 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 { if err != nil {
panic(err) panic(err)
} }
@@ -91,8 +93,8 @@ func main() {
case <-ctx.Done(): case <-ctx.Done():
return return
case tx := <-txCh: case tx := <-txCh:
if tx.Label == "okxdexroutev2" || tx.Label == "jupiterv6" || tx.Label == "dflow" { if tx.Label == "dbot" || tx.Label == "okxdexroutev2" {
fmt.Println("===============", tx.TxHash, tx.Label, tx.Event, tx.Token0Address, "token:", tx.Token0Amount, "parse time:", tx.ParseEnd.Sub(tx.ParseStart)) 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)
} }
} }
} }

View File

@@ -15,7 +15,7 @@ import (
const ( const (
rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d" rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
txSignature = "3hFamox2W1oWMwbRkfF5r9YiPULsdRsnR2TQsFDVtFCXf6cJ8ijGNgHGFmEbxEbVEryLg21sbt4qoGLwrPfvJ2UC" txSignature = "4gzWkLRWNLbkBdvyCqg2M4unWA7yg4DdMg8dGTnapw2USsefd9TjXVArhv22qJE9gtex46NwXC4xp1FtNZ1TmjAM"
labelFilter = "" labelFilter = ""
) )

View 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
}
}

View File

@@ -13,4 +13,5 @@ const (
SWQoSAgentAstralane = "astralane" SWQoSAgentAstralane = "astralane"
SWQoSAgentStellium = "stellium" SWQoSAgentStellium = "stellium"
SWQoSAgentSoyas = "soyas" SWQoSAgentSoyas = "soyas"
SWQoSAgentFast = "fast"
) )

View File

@@ -8,6 +8,7 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc" "github.com/gagliardetto/solana-go/rpc"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -23,6 +24,10 @@ type Client struct {
tableLoader *AddressTables tableLoader *AddressTables
subscription map[string]*SubscribeRequestFilterTransactions subscription map[string]*SubscribeRequestFilterTransactions
entriesFilter map[string]FilterParams
parser map[solana.PublicKey]Handler
pool *ants.Pool pool *ants.Pool
lastSlot uint64 lastSlot uint64
@@ -33,6 +38,8 @@ type ClientOpts struct {
blockStats bool blockStats bool
showTableLoaded bool showTableLoaded bool
logParseStats bool logParseStats bool
parser map[solana.PublicKey]Handler
} }
type ClientOption func(*ClientOpts) 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 { func BlocksStats(enable bool) ClientOption {
return func(opts *ClientOpts) { return func(opts *ClientOpts) {
opts.blockStats = enable opts.blockStats = enable
@@ -82,14 +95,31 @@ func NewShrederClient(
blockStats: false, blockStats: false,
showTableLoaded: true, showTableLoaded: true,
logParseStats: false, logParseStats: false,
parser: registered,
} }
for _, option := range options { for _, option := range options {
option(o) 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{ s := &Client{
conn: conn, conn: conn,
client: NewShrederServiceClient(conn), client: NewShrederServiceClient(conn),
subscription: subscription, subscription: subscription,
entriesFilter: filterParams,
parser: o.parser,
tableLoader: NewAddressTables(rpcClient, o.showTableLoaded), tableLoader: NewAddressTables(rpcClient, o.showTableLoaded),
pool: pool, pool: pool,
@@ -142,7 +172,16 @@ func (c *Client) ReadEntriesSync(ctx context.Context, txCh chan<- TxSignal) erro
} }
err = c.pool.Submit(func() { 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) { if err != nil && errors.Is(err, ants.ErrPoolOverload) {
logger.Warn("task pool is full") 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() { 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) { if err != nil && errors.Is(err, ants.ErrPoolOverload) {
logger.Warn("task pool is full") logger.Warn("task pool is full")
@@ -202,3 +246,11 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignal) error {
return err return err
} }
func parseAccountArray(accountArray []string) []solana.PublicKey {
var result []solana.PublicKey
for _, acc := range accountArray {
result = append(result, solana.MustPublicKeyFromBase58(acc))
}
return result
}

View 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
}

View File

@@ -3,12 +3,12 @@ package shreder
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"strings"
"github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go"
) )
var bloomRouterProgramID = solana.MustPublicKeyFromBase58("b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1") var bloomRouterProgramID = solana.MustPublicKeyFromBase58("b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1")
var pumpFunAccount = solana.MustPublicKeyFromBase58("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
type bloomRouterArgs struct { type bloomRouterArgs struct {
Side uint16 Side uint16
@@ -61,22 +61,27 @@ func parseBloomRouterInstruction(tx VersionedTransaction, instructionIndex int)
return nil, err return nil, err
} }
var ( var mint solana.PublicKey
mint solana.PublicKey foundPumpFun := false
ok bool for i, acctIdx := range instruction.Accounts {
)
for _, acctIdx := range instruction.Accounts {
key, err := tx.GetAccount(int(acctIdx)) key, err := tx.GetAccount(int(acctIdx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if strings.HasSuffix(key.String(), "pump") { if key.Equals(pumpFunAccount) {
mint = key if i+2 >= len(instruction.Accounts) {
ok = true return nil, 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 nil, err
}
mint = mintKey
foundPumpFun = true
break break
} }
} }
if !ok { if !foundPumpFun {
return nil, nil return nil, nil
} }

134
pkg/shreder/program_dbot.go Normal file
View 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
}

View File

@@ -72,6 +72,11 @@ const (
ActOpenPredictionsOrder ActOpenPredictionsOrder
ActScorchSwap ActScorchSwap
ActIncludeAccount ActIncludeAccount
ActDFLOWStabbleWeightedSwap
ActVertigoSwap
ActSetMinimumLegOutputs
ActSetMinimumLegPrices
) )
// DynamicRouteV1CandidateAction tags // DynamicRouteV1CandidateAction tags
@@ -104,7 +109,7 @@ type dflowSwapParams struct {
// bytes to skip for Action variants; only PumpFun* actions are decoded. // bytes to skip for Action variants; only PumpFun* actions are decoded.
func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) { func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
switch tag { switch tag {
case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2: case ActWhirlpoolsSwap, ActClearpoolsSwap, ActWhirlpoolsSwapV2, ActDFLOWStabbleWeightedSwap, ActVertigoSwap:
// amount u64 + bool + orchestrator_flags u8 // amount u64 + bool + orchestrator_flags u8
return nil, dec.SkipBytes(8 + 1 + 1) return nil, dec.SkipBytes(8 + 1 + 1)
case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap, case ActRaydiumAmmSwap, ActLifinityV2Swap, ActObricV2Swap,
@@ -181,6 +186,19 @@ func skipDflowAction(dec *bin.Decoder, tag uint8) (*pumpFunAction, error) {
return nil, dec.SkipBytes(8 + 16 + 1) return nil, dec.SkipBytes(8 + 16 + 1)
case ActIncludeAccount: case ActIncludeAccount:
return nil, nil 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: default:
return nil, fmt.Errorf("unsupported action tag %d", tag) return nil, fmt.Errorf("unsupported action tag %d", tag)
} }

View 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)
})
}
}

View File

@@ -122,6 +122,10 @@ func parseDlmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
if err != nil { if err != nil {
return nil, err 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])) userTokenOut, err := tx.GetAccount(int(instruction.Accounts[5]))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -220,6 +224,7 @@ func parseDlmmInstruction(tx VersionedTransaction, instructionIndex int) (TxSign
ExactSOL: exactSol, ExactSOL: exactSol,
ActiveBin: args.ActiveBin, ActiveBin: args.ActiveBin,
MaxPriceImpactBps: args.MaxPriceImpactBps, MaxPriceImpactBps: args.MaxPriceImpactBps,
LbPairAddress: lbPair.String(),
Block: tx.Block, Block: tx.Block,
Token0AmountUint64: token0AmountUint64, Token0AmountUint64: token0AmountUint64,
Token1AmountUint64: token1AmountUint64, Token1AmountUint64: token1AmountUint64,

View File

@@ -10,8 +10,8 @@ import (
var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") var flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
var ( var (
flasBuyTokensIX = []byte{0x00, 0x1, 0x4} flasBuyTokensIX = []byte{0x00, 0x1, 0x1b}
flasSellTokensIX = []byte{0x01, 0x1, 0x3} flasSellTokensIX = []byte{0x01, 0x1, 0x1a}
flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2} flasAmmBuyTokensIX = []byte{0x00, 0x2, 0x2}
flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2} flasAmmSellTokensIX = []byte{0x01, 0x2, 0x2}
) )
@@ -140,11 +140,11 @@ func parseFlasAmmBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal,
func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex] instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 9 { if len(instruction.Accounts) < 11 {
return nil, fmt.Errorf("accounts too short") 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 { if err != nil {
return nil, err return nil, err
} }
@@ -178,15 +178,15 @@ func parseFlasSell(tx VersionedTransaction, instructionIndex int) (*TxSignal, er
func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) { func parseFlasBuy(tx VersionedTransaction, instructionIndex int) (*TxSignal, error) {
instruction := tx.Instructions[instructionIndex] instruction := tx.Instructions[instructionIndex]
if len(instruction.Accounts) < 9 { if len(instruction.Accounts) < 11 {
return nil, fmt.Errorf("accounts too short") 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 { if err != nil {
return nil, err return nil, err
} }
user, err := tx.GetAccount(int(instruction.Accounts[0])) user, err := tx.GetAccount(int(instruction.Accounts[1]))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -157,6 +157,9 @@ const (
PerenaStar PerenaStar
JupiterRfqV2 JupiterRfqV2
GoonFiV2 GoonFiV2
Scorch
VaultLiquidUnstake
XOrca
) )
var swapKindNames = [122]string{"Saber", "SaberAddDecimalsDeposit", "SaberAddDecimalsWithdraw", "TokenSwap", "Sencha", "Step", "Cropper", var swapKindNames = [122]string{"Saber", "SaberAddDecimalsDeposit", "SaberAddDecimalsWithdraw", "TokenSwap", "Sencha", "Step", "Cropper",
@@ -316,7 +319,7 @@ func decodeSwap(dec *bin.Decoder) (Swap, error) {
BoopdotfunWrappedBuy, BoopdotfunWrappedSell, MeteoraDynamicBondingCurveSwapWithRemainingAccounts, BoopdotfunWrappedBuy, BoopdotfunWrappedSell, MeteoraDynamicBondingCurveSwapWithRemainingAccounts,
PumpWrappedBuyV2, PumpWrappedSellV2, PumpSwapBuyV2, PumpSwapSellV2, Aquifer, PumpWrappedBuyV3, PumpWrappedSellV3, PumpWrappedBuyV2, PumpWrappedSellV2, PumpSwapBuyV2, PumpSwapSellV2, Aquifer, PumpWrappedBuyV3, PumpWrappedSellV3,
PumpSwapBuyV3, PumpSwapSellV3, JupiterLendDeposit, JupiterLendRedeem, RaydiumV2, PumpSwapBuyV3, PumpSwapSellV3, JupiterLendDeposit, JupiterLendRedeem, RaydiumV2,
MeteoraDammV2WithRemainingAccounts, Obsidian, PumpWrappedBuyV4, PumpWrappedSellV4, CarrotIssue, CarrotRedeem: MeteoraDammV2WithRemainingAccounts, Obsidian, PumpWrappedBuyV4, PumpWrappedSellV4, CarrotIssue, CarrotRedeem, XOrca:
return out, nil return out, nil
// -------- bool payload -------- // -------- bool payload --------
@@ -406,9 +409,23 @@ func decodeSwap(dec *bin.Decoder) (Swap, error) {
return Swap{}, err return Swap{}, err
} }
return out, nil 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: default:
// Unknown/new variant: assume no payload (keeps decoder aligned for RoutePlanStepV2 if it really is no-payload). // 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 +1122,8 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
isBuy bool isBuy bool
isSell bool isSell bool
count int count int
sellPercent uint8
buyPercent uint8
) )
for _, step := range plan { for _, step := range plan {
if !isInputIdx0(step.InputIdx) { if !isInputIdx0(step.InputIdx) {
@@ -1113,9 +1132,11 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
if isPumpSwapSellKind(step.Swap.Kind) { if isPumpSwapSellKind(step.Swap.Kind) {
isSell = true isSell = true
count++ count++
sellPercent = step.Percent
} else if isPumpSwapBuyKind(step.Swap.Kind) { } else if isPumpSwapBuyKind(step.Swap.Kind) {
isBuy = true isBuy = true
count++ count++
buyPercent = step.Percent
} }
} }
if count == 0 { if count == 0 {
@@ -1137,6 +1158,9 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
if in > 0 { if in > 0 {
token0Amount = formatTokenAmount(in) token0Amount = formatTokenAmount(in)
} }
if sellPercent > 0 && sellPercent < 100 {
token0Amount = token0Amount.Mul(decimal.NewFromInt(int64(sellPercent))).Div(decimal.NewFromInt(100))
}
return &TxSignal{ return &TxSignal{
TxHash: tx.Signatures[0].String(), TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(), Maker: tx.StaticAccountKeys[0].String(),
@@ -1172,6 +1196,10 @@ func parseJupiterPumpAmmRoute(tx VersionedTransaction, instruction Instructions,
if in > 0 { if in > 0 {
token1Amount = formatSolAmount(in) 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{ return &TxSignal{
TxHash: tx.Signatures[0].String(), TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(), Maker: tx.StaticAccountKeys[0].String(),

View File

@@ -65,7 +65,11 @@ func TestDecodeRouteArg(t *testing.T) {
}, },
{ {
name: "Jupiter V6 RouteArg Test 1", name: "Jupiter V6 RouteArg Test 1",
hexData: "e517cb977ae3ad2a0200000028640001510000000000000000640102c09ee605000000005e1bc48efa000000d00700", hexData: "e517cb977ae3ad2a03000000646400017ab0b6c3d206f46577050000000c0000526401025f00640203bb628e2902000000338c430100000000320000",
},
{
name: "Jupiter V6 RouteArg Test 2",
hexData: "e517cb977ae3ad2a04000000642300024b00000000410002761acfb15ea9fdcd0501200204769358e96343759bf8014402046196591e1e020000f5bf6fe101000000d00700",
}, },
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -291,69 +291,189 @@ func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int
return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts)) return nil, fmt.Errorf("invalid account count: %d", len(ix.Accounts))
} }
var ( var (
inputAmount uint64 pumpAmmSellAmount uint64
routeCount int pumpAmmBuyAmount uint64
pumpFunSellAmount uint64
pumpFunBuyAmount uint64
pumpAmmSellCount int
pumpAmmBuyCount int
pumpFunSellCount int
pumpFunBuyCount int
) )
for _, route := range args.Routes { for _, route := range args.Routes {
if route.Index == 1 && (route.Dex == OKCV2_PumpfunammSell || if route.Index != 1 {
route.Dex == OKCV2_PumpfunSell2) { continue
routeCount++ }
inputAmount = args.AmountIn * uint64(route.Weight) / 10000 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 { if pumpAmmSellCount > 1 {
logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", routeCount) logger.Warn("pumpAmmSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "routeCount", pumpAmmSellCount)
return nil, nil 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 return nil, nil
} }
srcMint, err := tx.GetAccount(int(ix.Accounts[3])) out := make(TxSignalBatch, 0, 2)
var ( if pumpFunBuyAmount > 0 || pumpFunSellAmount > 0 {
srcIdx uint8 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(ix.Accounts) <= 15 {
if len(out) == 0 {
return nil, nil return nil, nil
} }
return out, nil
}
accounts := ix.Accounts[14:] accounts := ix.Accounts[14:]
var pumpAmmIdx uint8
for i, acctIdx := range accounts { for i, acctIdx := range accounts {
key, err := tx.GetAccount(int(acctIdx)) key, err := tx.GetAccount(int(acctIdx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if key.Equals(pumpAmmProgramID) { if key.Equals(pumpAmmProgramID) {
srcIdx = uint8(i + 6) pumpAmmIdx = uint8(i + 6)
break break
} }
} }
if srcIdx == 0 || int(srcIdx+1) >= len(accounts) { if pumpAmmIdx == 0 || int(pumpAmmIdx+1) >= len(accounts) {
if len(out) == 0 {
return nil, nil return nil, nil
} }
return out, nil
baseMint, err := tx.GetAccount(int(accounts[srcIdx])) }
baseMint, err := tx.GetAccount(int(accounts[pumpAmmIdx]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !baseMint.Equals(srcMint) { quoteMint, err := tx.GetAccount(int(accounts[pumpAmmIdx+1]))
return nil, nil
}
quoteMint, err := tx.GetAccount(int(accounts[srcIdx+1]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !quoteMint.Equals(solana.WrappedSol) { if !quoteMint.Equals(solana.WrappedSol) {
if len(out) == 0 {
return nil, nil return nil, nil
} }
return out, nil
return TxSignalBatch{&TxSignal{ }
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(), TxHash: tx.Signatures[0].String(),
Maker: tx.StaticAccountKeys[0].String(), Maker: tx.StaticAccountKeys[0].String(),
Token0Address: baseMint.String(), Token0Address: baseMint.String(),
Token1Address: wsolMint, 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, Token1Amount: decimal.Zero,
Event: "sell", Event: "sell",
Program: "PumpAMM", Program: "PumpAMM",
@@ -361,7 +481,15 @@ func parseOkxDexRouteV2Instruction(tx VersionedTransaction, instructionIndex int
IsToken2022: false, IsToken2022: false,
IsMayhemMode: false, IsMayhemMode: false,
ExactSOL: false, ExactSOL: false,
Token0AmountUint64: inputAmount, Token0AmountUint64: pumpAmmSellAmount,
Token1AmountUint64: 0, Token1AmountUint64: 0,
}}, nil })
}
}
}
if len(out) == 0 {
return nil, nil
}
return out, nil
} }

View 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
}

View File

@@ -44,7 +44,10 @@ type TxSignal struct {
IsProcessed bool `json:"is_processed"` IsProcessed bool `json:"is_processed"`
IsToken2022 bool `json:"is_token2022"` IsToken2022 bool `json:"is_token2022"`
IsMayhemMode bool `json:"is_mayhem_mode"` IsMayhemMode bool `json:"is_mayhem_mode"`
TxFee decimal.Decimal `json:"tx_fee"` CUPrice decimal.Decimal `json:"cu_price"`
CULimit decimal.Decimal `json:"cu_limit"`
SWQoSAgent string `json:"swqos_agent"`
SWQoSTips decimal.Decimal `json:"swqos_tips"`
ExactSOL bool `json:"exact_in"` ExactSOL bool `json:"exact_in"`
@@ -53,6 +56,8 @@ type TxSignal struct {
ActiveBin int32 `json:"active_bin"` ActiveBin int32 `json:"active_bin"`
// MaxPriceImpactBps is the price impact guard for swap_with_price_impact(2). // MaxPriceImpactBps is the price impact guard for swap_with_price_impact(2).
MaxPriceImpactBps uint16 `json:"max_price_impact_bps"` MaxPriceImpactBps uint16 `json:"max_price_impact_bps"`
//
LbPairAddress string `json:"lb_pair_address"`
// parsed values // parsed values
Token0AmountUint64 uint64 `json:"-"` Token0AmountUint64 uint64 `json:"-"`

View File

@@ -3,6 +3,7 @@ package shreder
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
@@ -10,6 +11,7 @@ import (
"strings" "strings"
"github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go"
"github.com/samlior/libsam/v2/pkg/consts"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
) )
@@ -28,32 +30,39 @@ type FillAccount interface {
} }
func init() { func init() {
for account := range parsedMap { for account := range registered {
parseProgram = append(parseProgram, account) defaultFilterAccount = append(defaultFilterAccount, account)
} }
//"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority //"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority
//"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config //"5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config
//"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program //"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program
parseProgram = append(parseProgram, defaultFilterAccount = append(defaultFilterAccount,
solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"), solana.MustPublicKeyFromBase58("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"),
solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"), solana.MustPublicKeyFromBase58("5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx"),
solana.MustPublicKeyFromBase58("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"), 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[:]) return bytes.Compare(a[:], b[:])
}) })
} }
var ( type FilterParams struct {
parseProgram []solana.PublicKey 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"}, pumpProgramID: {parsePumpInstruction, "pump"},
azczProgramID: {parseAzczInstruction, "azcz"}, azczProgramID: {parseAzczInstruction, "azcz"},
f5tfProgramID: {parseF5tfInstruction, "f5tf"}, f5tfProgramID: {parseF5tfInstruction, "f5tf"},
flasProgramID: {parseFlasInstruction, "flas"}, flasProgramID: {parseFlasInstruction, "flas"},
photonProgramID: {parsePhotonInstruction, "photon"}, photonProgramID: {parsePhotonInstruction, "photon"},
pumpAmmProgramID: {parsePumpAmmInstruction, "pumpamm"}, pumpAmmProgramID: {parsePumpAmmInstruction, "pumpamm"},
binanceWalletProgramID: {parseBinanceWalletInstruction, "binancewallet"},
boboProgramID: {parseBoboInstruction, "bobo"}, boboProgramID: {parseBoboInstruction, "bobo"},
qtkvProgramID: {parseQtkvInstruction, "qtkv"}, qtkvProgramID: {parseQtkvInstruction, "qtkv"},
fjszProgramID: {parseFjszInstruction, "fjsz"}, fjszProgramID: {parseFjszInstruction, "fjsz"},
@@ -65,6 +74,8 @@ var (
bonkProgramID: {parseBonkInstruction, "bonk"}, bonkProgramID: {parseBonkInstruction, "bonk"},
bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"}, bloomRouterProgramID: {parseBloomRouterInstruction, "bloomrouter"},
dlmmProgramID: {parseDlmmInstruction, "dlmm"}, dlmmProgramID: {parseDlmmInstruction, "dlmm"},
dbotProgramID: {parseDbotInstruction, "dbot"},
tradewizProgramID: {parseTradewizInstruction, "tradewiz"},
} }
) )
@@ -72,14 +83,21 @@ func ParseTransactionForSubscribe(ctx context.Context, update *SubscribeUpdateTr
versioned, err := toVersionedTransaction(update) versioned, err := toVersionedTransaction(update)
if err != nil { if err != nil {
logger.Debug("txparser: failed to convert to versioned transaction", "error", err) logger.Debug("txparser: failed to convert to versioned transaction", "error", err)
if done != nil {
close(done) close(done)
}
return return
} }
ParseTransaction(ctx, versioned, loader, parsed) ParseTransaction(ctx, versioned, loader, parsed)
if done != nil {
close(done) close(done)
}
} }
var VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111") var (
ComputeBudgetProgram = solana.MustPublicKeyFromBase58("ComputeBudget111111111111111111111111111111")
VoteProgram = solana.MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
)
func FilterTransactionForEntries(versioned VersionedTransaction) bool { func FilterTransactionForEntries(versioned VersionedTransaction) bool {
if len(versioned.Instructions) >= 1 { if len(versioned.Instructions) >= 1 {
@@ -92,7 +110,7 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
// accounts filter? // accounts filter?
include := false include := false
for _, key := range versioned.StaticAccountKeys { 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[:]) return bytes.Compare(key[:], key2[:])
}) })
if include { if include {
@@ -102,6 +120,53 @@ func FilterTransactionForEntries(versioned VersionedTransaction) bool {
return !include 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) { func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader io.Reader, loader *AddressTables, parsed chan<- TxSignal) {
err := entriesToVersionedTransaction(slot, entriesReader, func(versioned VersionedTransaction) { err := entriesToVersionedTransaction(slot, entriesReader, func(versioned VersionedTransaction) {
// filter out vote transactions // filter out vote transactions
@@ -116,8 +181,7 @@ func ParseTransactionForEntries(ctx context.Context, slot uint64, entriesReader
} }
} }
func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal) { func ParseTransactionWithHandler(ctx context.Context, versioned VersionedTransaction, loader *AddressTables, parsed chan<- TxSignal, handlers map[solana.PublicKey]Handler) {
// staticKeys := versioned.Message.StaticAccountKeys
if loader != nil && len(versioned.AddressTableLookups) > 0 { if loader != nil && len(versioned.AddressTableLookups) > 0 {
lookupTableOk := true lookupTableOk := true
for _, lookups := range versioned.AddressTableLookups { for _, lookups := range versioned.AddressTableLookups {
@@ -136,13 +200,54 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
} }
} }
cuPrice := decimal.Zero
swqosAgent := ""
swqosTips := decimal.Zero
cuLimit := decimal.Zero
for _, instruction := range versioned.Instructions {
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
if err != nil {
continue
}
if program.Equals(ComputeBudgetProgram) &&
len(instruction.Data) == 9 {
if instruction.Data[0] == 0x03 {
cuPriceUint64 := binary.LittleEndian.Uint64(instruction.Data[1:9])
cuPrice = formatCUPrice(cuPriceUint64)
} else if instruction.Data[0] == 0x02 {
cuLimitUint32 := binary.LittleEndian.Uint32(instruction.Data[1:4])
cuLimit = formatCUPrice(uint64(cuLimitUint32))
}
}
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 { for i, instruction := range versioned.Instructions {
//load from address table //load from address table
program, err := versioned.GetAccount(int(instruction.ProgramIDIndex)) program, err := versioned.GetAccount(int(instruction.ProgramIDIndex))
if err != nil { if err != nil {
continue continue
} }
handler, ok := parsedMap[program] handler, ok := handlers[program]
if !ok { if !ok {
continue continue
} }
@@ -160,6 +265,10 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
} }
one.Label = handler.Label one.Label = handler.Label
one.Block = versioned.Block one.Block = versioned.Block
one.CUPrice = cuPrice
one.CULimit = cuLimit
one.SWQoSAgent = swqosAgent
one.SWQoSTips = swqosTips
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
@@ -173,6 +282,11 @@ func ParseTransaction(ctx context.Context, versioned VersionedTransaction, loade
return 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) { func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransaction, error) {
if update == nil || update.Transaction == nil || update.Transaction.Message == nil { if update == nil || update.Transaction == nil || update.Transaction.Message == nil {
return VersionedTransaction{}, fmt.Errorf("transaction is nil") return VersionedTransaction{}, fmt.Errorf("transaction is nil")
@@ -224,6 +338,11 @@ func toVersionedTransaction(update *SubscribeUpdateTransaction) (VersionedTransa
return versioned, nil 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 { func formatTokenAmount(amount uint64) decimal.Decimal {
val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0) val := decimal.NewFromBigInt(new(big.Int).SetUint64(amount), 0)
return val.Div(decimal.NewFromInt(1_000_000)) return val.Div(decimal.NewFromInt(1_000_000))