├── .env ├── README.md ├── bonding-curve.go ├── buy-coin.go ├── go.mod ├── go.sum ├── handle-buy-coin.go ├── handle-sell-coin.go ├── hash.go ├── images └── bot-running.jpg ├── main.go ├── monitor-mints.go ├── pkg └── jito-go │ ├── LICENSE │ ├── README.md │ ├── clients │ ├── blockengine_client │ │ ├── blockengine.go │ │ ├── blockengine_test.go │ │ └── types.go │ ├── geyser_client │ │ ├── geyser.go │ │ ├── geyser_test.go │ │ └── types.go │ ├── relayer_client │ │ ├── relayer.go │ │ ├── relayer_test.go │ │ └── types.go │ ├── searcher_client │ │ ├── searcher.go │ │ ├── searcher_test.go │ │ └── types.go │ └── shredstream_client │ │ ├── shredstream.go │ │ ├── shredstream_test.go │ │ └── types.go │ ├── constants.go │ ├── go.mod │ ├── go.sum │ ├── pkg │ ├── convert.go │ ├── grpc.go │ ├── token_authenticator.go │ ├── types.go │ └── util.go │ ├── proto │ ├── auth.pb.go │ ├── auth_grpc.pb.go │ ├── block.pb.go │ ├── block_engine.pb.go │ ├── block_engine_grpc.pb.go │ ├── bundle.pb.go │ ├── confirmed_block.pb.go │ ├── geyser.pb.go │ ├── geyser_grpc.pb.go │ ├── packet.pb.go │ ├── relayer.pb.go │ ├── relayer_grpc.pb.go │ ├── searcher.pb.go │ ├── searcher_grpc.pb.go │ ├── shared.pb.go │ ├── shredstream.pb.go │ ├── shredstream_grpc.pb.go │ ├── trace_shred.pb.go │ └── transaction_by_addr.pb.go │ ├── scripts │ ├── run.bat │ └── run.sh │ └── taskfile.yml ├── pump-fun-idl.json ├── pump ├── Buy.go ├── Buy_test.go ├── Create.go ├── Create_test.go ├── Initialize.go ├── Initialize_test.go ├── Sell.go ├── Sell_test.go ├── SetParams.go ├── SetParams_test.go ├── Withdraw.go ├── Withdraw_test.go ├── accounts.go ├── instructions.go ├── testing_utils.go └── types.go ├── sell-coin.go ├── structs.go ├── tip.go └── utils.go /.env: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY=XXXXXXX 2 | PROXY_URL=XXXXX -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Pump-Fun Sniper Bot 3 | 4 | This repository contains the codebase for the Pump-Fun Sniper Bot developed by [@licketyspliket](https://www.twitter.com/licketyspliket) and me. Although this project is archived and not intended for further iteration, this README provides a comprehensive overview to help anyone who may want to understand or run the bot. 5 | 6 | ## Overview 7 | 8 | The Pump-Fun Sniper Bot is designed to interact with the Solana blockchain, purchasing newly minted coins and managing them, based on our replication of orcACR...'s strategy. I detail the strategy more in my first blog post on this project, which can be found [here](https://www.mikem.codes/if-you-aint-first-youre-last-2/). 9 | 10 | ![Bot Startup](images/bot-running.jpg) 11 | ## Configuration 12 | 13 | ### Environment Variables 14 | 15 | - `PRIVATE_KEY`: The bot pulls the bot wallet's private key from this environment variable. 16 | - `PROXY_URL`: Set this to an https proxy if you want to proxy the main RPC client 17 | 18 | ### Main Configuration 19 | 20 | The main configuration values for the bot are located in `main.go` and can be edited as needed. 21 | 22 | - **Public RPCs**: A slice of public RPC URLs that can be used to help transmit transactions can be modified in the `sendTxRPCs` string slice variable. 23 | - **RPC and WebSocket URLs**: Set `rpcURL` and `wsURL` to their proper values for a high-performance Solana RPC (Note: free/cheap RPC services will likely be ratelimited immediately due to the number of requests needed to vet coins and their creators). 24 | - **MySQL Database**: Ensure you have an instantiated MySQL database with information on coins created. Modify the credentials below as needed: 25 | ```go 26 | sql.Open("mysql", "root:XXXXXX!@/CoinTrades") 27 | ``` 28 | 29 | ### Bot Instantiation 30 | 31 | The bot is instantiated with the following parameters: 32 | 33 | ```go 34 | // Purchase coins with 0.05 Solana, priority fee of 200000 microlamports 35 | bot, err := NewBot(rpcURL, wsURL, privateKey, db, 0.05, 200000) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | ``` 40 | ### Jito Integration 41 | 42 | To remove Jito integration, comment out the following block: 43 | 44 | ```go 45 | if err := bot.beginJito(); err != nil { 46 | log.Fatal("Error Starting Jito", err) 47 | } 48 | ``` 49 | 50 | ## Installation and Running the Bot 51 | 52 | 1. **Clone the Repository**: 53 | ```sh 54 | git clone https://github.com/1fge/pump-fun-sniper-bot.git 55 | cd pump-fun-sniper-bot 56 | ``` 57 | 58 | 2. **Install Dependencies**: 59 | Ensure you have [Go](https://go.dev/doc/install) installed. Then, run: 60 | ```sh 61 | go mod tidy 62 | ``` 63 | 64 | 3. **Set Environment Variables**: 65 | Ensure `PRIVATE_KEY` is set in your environment. 66 | 67 | 4. **Edit Configuration**: 68 | Modify the RPC URLs, WebSocket URLs, and MySQL database credentials in `main.go` as needed. 69 | 70 | 5. **Run the Bot**: 71 | ```sh 72 | go run . 73 | ``` 74 | 75 | ## Additional Information 76 | 77 | - **Solana RPC and WebSocket**: Ensure you are using high-performance RPC and WebSocket URLs for optimal performance. 78 | - **MySQL Database**: Make sure your MySQL database is properly set up and accessible with the provided credentials. 79 | - **Jito Integration**: Optional integration for improved transaction handling. 80 | 81 | ## Acknowledgements 82 | 83 | A special thank you to [Gagliardetto](https://www.github.com/Gagliardetto) for creating the open-source Solana Go packages that were instrumental in developing this bot. The code we used and built on from him can be found in the `pkg/jito-go` directory, as well as the output from his `anchor-go` package based on the Pump.fun IDL, which is found in the `pump` directory. 84 | 85 | Also, a huge thank you to [weeaa](https://www.github.com/weeaa) for their work on [Jito Go SDK](https://www.github.com/weeaa/jito-go), which we used to handle all interactions with Jito. 86 | 87 | ## Learn More 88 | 89 | Read more about the development of this project on my [blog](https://www.mikem.codes/if-you-aint-first-youre-last-reverse-engineering-a-leading-pump-fun-sniper-bot-part-2/). 90 | -------------------------------------------------------------------------------- /bonding-curve.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "fmt" 7 | "math/big" 8 | 9 | "github.com/gagliardetto/solana-go" 10 | "github.com/gagliardetto/solana-go/rpc" 11 | ) 12 | 13 | // BondingCurveData holds the relevant information decoded from the on-chain data. 14 | type BondingCurveData struct { 15 | RealTokenReserves *big.Int 16 | VirtualTokenReserves *big.Int 17 | VirtualSolReserves *big.Int 18 | } 19 | 20 | func (b *BondingCurveData) String() string { 21 | return fmt.Sprintf("RealTokenReserves=%s, VirtualTokenReserves=%s, VirtualSolReserves=%s", b.RealTokenReserves, b.VirtualTokenReserves, b.VirtualSolReserves) 22 | } 23 | 24 | // fetchBondingCurve fetches the bonding curve data from the blockchain and decodes it. 25 | func (b *Bot) fetchBondingCurve(bondingCurvePubKey solana.PublicKey) (*BondingCurveData, error) { 26 | accountInfo, err := b.rpcClient.GetAccountInfoWithOpts(context.TODO(), bondingCurvePubKey, &rpc.GetAccountInfoOpts{Encoding: solana.EncodingBase64, Commitment: rpc.CommitmentProcessed}) 27 | if err != nil || accountInfo.Value == nil { 28 | return nil, fmt.Errorf("FBCD: failed to get account info: %w", err) 29 | } 30 | 31 | data := accountInfo.Value.Data.GetBinary() 32 | if len(data) < 24 { 33 | return nil, fmt.Errorf("FBCD: insufficient data length") 34 | } 35 | 36 | // Decode the bonding curve data assuming it follows little-endian format 37 | realTokenReserves := big.NewInt(0).SetUint64(binary.LittleEndian.Uint64(data[0:8])) 38 | virtualTokenReserves := big.NewInt(0).SetUint64(binary.LittleEndian.Uint64(data[8:16])) 39 | virtualSolReserves := big.NewInt(0).SetUint64(binary.LittleEndian.Uint64(data[16:24])) 40 | 41 | return &BondingCurveData{ 42 | RealTokenReserves: realTokenReserves, 43 | VirtualTokenReserves: virtualTokenReserves, 44 | VirtualSolReserves: virtualSolReserves, 45 | }, nil 46 | } 47 | 48 | // calculateBuyQuote calculates how many tokens can be purchased given a specific amount of SOL, bonding curve data, and percentage. 49 | func calculateBuyQuote(solAmount uint64, bondingCurve *BondingCurveData, percentage float64) *big.Int { 50 | // Convert solAmount to *big.Int 51 | solAmountBig := big.NewInt(int64(solAmount)) 52 | 53 | // Clone bonding curve data to avoid mutations 54 | virtualSolReserves := new(big.Int).Set(bondingCurve.VirtualSolReserves) 55 | virtualTokenReserves := new(big.Int).Set(bondingCurve.VirtualTokenReserves) 56 | 57 | // Compute the new virtual reserves 58 | newVirtualSolReserves := new(big.Int).Add(virtualSolReserves, solAmountBig) 59 | invariant := new(big.Int).Mul(virtualSolReserves, virtualTokenReserves) 60 | newVirtualTokenReserves := new(big.Int).Div(invariant, newVirtualSolReserves) 61 | 62 | // Calculate the tokens to buy 63 | tokensToBuy := new(big.Int).Sub(virtualTokenReserves, newVirtualTokenReserves) 64 | 65 | // Apply the percentage reduction (e.g., 95% or 0.95) 66 | // Convert the percentage to a multiplier (0.95) and apply to tokensToBuy 67 | percentageMultiplier := big.NewFloat(percentage) 68 | tokensToBuyFloat := new(big.Float).SetInt(tokensToBuy) 69 | finalTokens := new(big.Float).Mul(tokensToBuyFloat, percentageMultiplier) 70 | 71 | // Convert the result back to *big.Int 72 | finalTokensBig, _ := finalTokens.Int(nil) 73 | 74 | return finalTokensBig 75 | } 76 | -------------------------------------------------------------------------------- /buy-coin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | "strings" 10 | "time" 11 | 12 | "github.com/1fge/pump-fun-sniper-bot/pump" 13 | "github.com/gagliardetto/solana-go" 14 | associatedtokenaccount "github.com/gagliardetto/solana-go/programs/associated-token-account" 15 | cb "github.com/gagliardetto/solana-go/programs/compute-budget" 16 | ) 17 | 18 | var ( 19 | // compute units never seem to get close to exceeding 70,000 so no need to set higher 20 | computeUnitLimits uint32 = 70000 21 | errNilCoin = errors.New("Nil Coin") 22 | errLateToCoin = errors.New("Coin has multiple buyers (BCD)") 23 | ) 24 | 25 | // BuyCoin handles the code for purchasing a single coin, updating program 26 | // state depending on the success of the purchase or not 27 | func (b *Bot) BuyCoin(coin *Coin) error { 28 | var shouldCreateATA bool 29 | defer coin.setExitedBuyCoinTrue() 30 | 31 | var instructions []solana.Instruction 32 | 33 | if coin == nil { 34 | return errNilCoin 35 | } 36 | 37 | // coin not nil, display buy status 38 | buyStatus := fmt.Sprintf("Attempting to buy %s (%v)", coin.mintAddr.String(), time.Since(coin.pickupTime)) 39 | b.status(buyStatus) 40 | 41 | ataAddress, err := b.calculateATAAddress(coin) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | if b.skipATALookup { 47 | shouldCreateATA = true 48 | } else { 49 | coin.status("Checking associated token: " + ataAddress.String()) 50 | shouldCreateATA, err = b.shouldCreateATA(ataAddress) 51 | if err != nil { 52 | return err 53 | } 54 | } 55 | 56 | coin.status("Fetching bonding curve") 57 | bcd, err := b.fetchBondingCurve(coin.tokenBondingCurve) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | // protect us from stale data, bad buy price 63 | // by checking if someone else has already purchased through BCD 64 | coin.status(fmt.Sprintf("Fetched bonding curve, (%s)", bcd.String())) 65 | if coin.lateToBuy(bcd) { 66 | return errLateToCoin 67 | } 68 | 69 | // determine num tokens to buy based on sol buy amount, 70 | // set very low slippage tolerance (2% max slippage) so we ensure we 71 | // enter in position as second buyer 72 | coin.buyPrice = b.buyAmountLamport 73 | tokensToBuy := calculateBuyQuote(b.buyAmountLamport, bcd, 0.98) 74 | buyInstruction := b.createBuyInstruction(tokensToBuy, coin, *ataAddress) 75 | 76 | // create priority fee instructions 77 | culInst := cb.NewSetComputeUnitLimitInstruction(uint32(computeUnitLimits)) 78 | cupInst := cb.NewSetComputeUnitPriceInstruction(b.feeMicroLamport) 79 | 80 | if shouldCreateATA { 81 | _, createAtaInstruction, err := b.createATA(coin) 82 | if err != nil { 83 | return err 84 | } 85 | instructions = []solana.Instruction{cupInst.Build(), culInst.Build(), createAtaInstruction, buyInstruction.Build()} 86 | } else { 87 | instructions = []solana.Instruction{cupInst.Build(), culInst.Build(), buyInstruction.Build()} 88 | } 89 | 90 | enableJito := b.jitoManager.isJitoLeader() 91 | if enableJito { 92 | coin.status("Jito leader, setting tip & removing priority fee inst") 93 | tipInst, err := b.jitoManager.generateTipInstruction() 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | 98 | instructions = append(instructions, tipInst) 99 | 100 | // IMPORTANT: remove priority fee when we jito tip 101 | instructions = instructions[1:] 102 | } 103 | 104 | coin.status("Creating transaction") 105 | tx, err := b.createTransaction(instructions...) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | coin.status("Sending transaction") 111 | if _, err = b.signAndSendTx(tx, enableJito); err != nil { 112 | if !strings.Contains(err.Error(), "transaction has already been processed") { 113 | return err 114 | } 115 | } 116 | 117 | // notify chans we have purchased & set amount of owned tokens 118 | coin.botPurchased = true 119 | coin.tokensHeld = tokensToBuy 120 | coin.associatedTokenAccount = *ataAddress 121 | coin.buyTransactionSignature = &tx.Signatures[0] 122 | 123 | return nil 124 | } 125 | 126 | func (c *Coin) setExitedBuyCoinTrue() { 127 | c.exitedBuyCoin = true 128 | } 129 | 130 | // calculateATAAddress calculates the associated token account address for the bot's public key and the coin's mint address. 131 | // The address is a deterministic address based on the public key and the mint address. 132 | func (b *Bot) calculateATAAddress(coin *Coin) (*solana.PublicKey, error) { 133 | coin.status("Calculating associated token address") 134 | 135 | ata, _, err := solana.FindAssociatedTokenAddress(b.privateKey.PublicKey(), coin.mintAddr) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | return &ata, nil 141 | } 142 | 143 | // shouldCreateATA checks if the associated token account for the mint and our bot's public key exists. 144 | func (b *Bot) shouldCreateATA(ataAddress *solana.PublicKey) (bool, error) { 145 | _, err := b.rpcClient.GetAccountInfo(context.TODO(), *ataAddress) 146 | if err == nil { 147 | return false, nil 148 | } 149 | 150 | return true, nil 151 | } 152 | 153 | // createATA creates associated token account for the mint and our bot's public key. 154 | // it also validateAndBuilds the instruction for creating the new address 155 | // NOTE: we always assume we do not have an ATA for the coin since we never buy twice 156 | func (b *Bot) createATA(coin *Coin) (solana.PublicKey, *associatedtokenaccount.Instruction, error) { 157 | var botPubKey solana.PublicKey = b.privateKey.PublicKey() 158 | var defaultPubKey solana.PublicKey = solana.PublicKey{} 159 | 160 | ata, _, err := solana.FindAssociatedTokenAddress(botPubKey, coin.mintAddr) 161 | if err != nil { 162 | return defaultPubKey, nil, err 163 | } 164 | 165 | // Create the associated token account instruction 166 | createATAInstruction, err := associatedtokenaccount.NewCreateInstruction( 167 | botPubKey, // Payer 168 | botPubKey, // Wallet owner 169 | coin.mintAddr, // Token mint 170 | ).ValidateAndBuild() 171 | if err != nil { 172 | return defaultPubKey, nil, err 173 | } 174 | 175 | return ata, createATAInstruction, nil 176 | } 177 | 178 | func (b *Bot) createBuyInstruction(tokensToBuy *big.Int, coin *Coin, ata solana.PublicKey) *pump.Buy { 179 | return pump.NewBuyInstruction( 180 | tokensToBuy.Uint64(), 181 | b.buyAmountLamport, 182 | globalAddr, 183 | feeRecipient, 184 | coin.mintAddr, 185 | coin.tokenBondingCurve, 186 | coin.associatedBondingCurve, 187 | ata, 188 | b.privateKey.PublicKey(), 189 | solana.SystemProgramID, 190 | solana.TokenProgramID, 191 | rent, 192 | coin.eventAuthority, 193 | pumpProgramID, 194 | ) 195 | } 196 | 197 | func (b *Bot) createTransaction(instructions ...solana.Instruction) (*solana.Transaction, error) { 198 | // Prepare the transaction with both the associated token account creation and the buy instructions 199 | return solana.NewTransaction( 200 | instructions, 201 | *b.blockhash, 202 | solana.TransactionPayer(b.privateKey.PublicKey()), 203 | ) 204 | } 205 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/1fge/pump-fun-sniper-bot 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.5 6 | 7 | replace github.com/1fge/pump-fun-sniper-bot/pkg/jito-go => ./pkg/jito-go 8 | 9 | replace github.com/1fge/pump-fun-sniper-bot/pump => ./pump 10 | 11 | require ( 12 | github.com/1fge/pump-fun-sniper-bot/pkg/jito-go v0.0.0-00010101000000-000000000000 13 | github.com/davecgh/go-spew v1.1.1 14 | github.com/gagliardetto/binary v0.8.0 15 | github.com/gagliardetto/gofuzz v1.2.2 16 | github.com/gagliardetto/solana-go v1.11.0 17 | github.com/gagliardetto/treeout v0.1.4 18 | github.com/go-sql-driver/mysql v1.8.1 19 | github.com/gookit/color v1.5.4 20 | github.com/joho/godotenv v1.5.1 21 | github.com/stretchr/testify v1.9.0 22 | ) 23 | 24 | require ( 25 | filippo.io/edwards25519 v1.1.0 // indirect 26 | github.com/benbjohnson/clock v1.3.5 // indirect 27 | github.com/blendle/zapdriver v1.3.1 // indirect 28 | github.com/buger/jsonparser v1.1.1 // indirect 29 | github.com/fatih/color v1.16.0 // indirect 30 | github.com/google/uuid v1.6.0 // indirect 31 | github.com/gorilla/rpc v1.2.0 // indirect 32 | github.com/gorilla/websocket v1.5.1 // indirect 33 | github.com/json-iterator/go v1.1.12 // indirect 34 | github.com/klauspost/compress v1.17.8 // indirect 35 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 36 | github.com/mattn/go-colorable v0.1.13 // indirect 37 | github.com/mattn/go-isatty v0.0.20 // indirect 38 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 40 | github.com/modern-go/reflect2 v1.0.2 // indirect 41 | github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect 42 | github.com/mr-tron/base58 v1.2.0 // indirect 43 | github.com/pmezard/go-difflib v1.0.0 // indirect 44 | github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect 45 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect 46 | go.mongodb.org/mongo-driver v1.15.0 // indirect 47 | go.uber.org/multierr v1.11.0 // indirect 48 | go.uber.org/ratelimit v0.3.1 // indirect 49 | go.uber.org/zap v1.27.0 // indirect 50 | golang.org/x/crypto v0.23.0 // indirect 51 | golang.org/x/net v0.25.0 // indirect 52 | golang.org/x/sys v0.20.0 // indirect 53 | golang.org/x/term v0.20.0 // indirect 54 | golang.org/x/text v0.15.0 // indirect 55 | golang.org/x/time v0.5.0 // indirect 56 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect 57 | google.golang.org/grpc v1.63.2 // indirect 58 | google.golang.org/protobuf v1.34.1 // indirect 59 | gopkg.in/yaml.v3 v3.0.1 // indirect 60 | ) 61 | -------------------------------------------------------------------------------- /handle-buy-coin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/gagliardetto/solana-go" 10 | "github.com/gagliardetto/solana-go/programs/token" 11 | "github.com/gagliardetto/solana-go/rpc" 12 | 13 | pump "github.com/1fge/pump-fun-sniper-bot/pump" 14 | ) 15 | 16 | type instPair struct { 17 | tx *solana.Transaction 18 | meta *rpc.TransactionMeta 19 | } 20 | 21 | // HandleBuyCoins is run as a goroutine which keeps waiting for 22 | // new coins to enter the `coinsToBuy` channel 23 | // we will start the buying process async and update our coins map 24 | // with the coin at the same time 25 | func (b *Bot) HandleBuyCoins() { 26 | for coin := range b.coinsToBuy { 27 | go b.purchaseCoin(coin) 28 | } 29 | } 30 | 31 | func (b *Bot) purchaseCoin(coin *Coin) { 32 | if coin == nil { 33 | return 34 | } 35 | 36 | // add in new coin to pending coins 37 | b.addNewPendingCoin(coin) 38 | 39 | // immediately start listening for a creator sell 40 | go b.listenCreatorSell(coin) 41 | 42 | if err := b.BuyCoin(coin); err != nil { 43 | b.statusy("Error Buying Coin: " + err.Error()) 44 | return 45 | } 46 | 47 | fmt.Println("Purchased Coin", coin.mintAddr.String()) 48 | } 49 | 50 | func (b *Bot) addNewPendingCoin(coin *Coin) { 51 | b.pendingCoinsLock.Lock() 52 | defer b.pendingCoinsLock.Unlock() 53 | 54 | mintAddr := coin.mintAddr.String() 55 | b.pendingCoins[mintAddr] = coin 56 | } 57 | 58 | func (b *Bot) listenCreatorSell(coin *Coin) { 59 | // subscribe to our creator ATA with our ws client 60 | defer coin.setExitedCreatorListenerTrue() 61 | 62 | sub, err := b.wsClient.AccountSubscribe(coin.creatorATA, rpc.CommitmentConfirmed) 63 | if err != nil { 64 | log.Printf("Failed to subscribe to logs: %v", err) 65 | b.setCreatorSold(coin) 66 | return 67 | } 68 | 69 | defer sub.Unsubscribe() 70 | 71 | for { 72 | // act as signal to fetch latest transactions 73 | _, err := sub.Recv() 74 | if err != nil { 75 | log.Printf("Error receiving AccountSubscribe: %v\n", err) 76 | b.setCreatorSold(coin) 77 | return 78 | } 79 | 80 | // if we exited BuyCoin & didn't purchase, exit listener 81 | // alternatively, if we purchased but don't hold tokens any longer, exit listener 82 | if (coin.exitedBuyCoin && !coin.botPurchased) || (coin.botPurchased && !coin.botHoldsTokens()) { 83 | fmt.Println("No buy recorded or bot already sold tokens, stopping listener") 84 | return 85 | } 86 | 87 | // variable which allows us to see if we managed to check ATA activity 88 | // if we didn't mark as sold since we cannot trust data we have 89 | 90 | // check 10 times to allow catching up with new data / timeouts, if RPC experiencing issues 91 | for checkAttempts := 0; checkAttempts < 10; checkAttempts++ { 92 | instPairs, err := b.fetchCreatorATATrans(coin) 93 | if err != nil { 94 | log.Printf("Error Fetching Creator Transactions, continuing to next loop: " + err.Error() + "\n") 95 | continue 96 | } 97 | 98 | if b.isSellOrTransfer(instPairs, coin) { 99 | b.status(fmt.Sprintf("Detected Sale / Transfer, Marking as sold %s", coin.mintAddr.String())) 100 | b.setCreatorSold(coin) 101 | return 102 | } 103 | 104 | time.Sleep(200 * time.Millisecond) 105 | } 106 | 107 | fmt.Println("Activity for ATA", coin.creatorATA.String(), "was not sell/transfer") 108 | } 109 | } 110 | 111 | func (c *Coin) setExitedCreatorListenerTrue() { 112 | c.exitedCreatorListener = true 113 | } 114 | 115 | // update that creator has sold (used on actual sell / transfer & err) 116 | func (b *Bot) setCreatorSold(coin *Coin) { 117 | b.pendingCoinsLock.Lock() 118 | defer b.pendingCoinsLock.Unlock() 119 | 120 | mintAddr := coin.mintAddr.String() 121 | if _, ok := b.pendingCoins[mintAddr]; ok { 122 | b.pendingCoins[mintAddr].creatorSold = true 123 | } 124 | } 125 | 126 | // fetchCreatorATATrans pulls latest 3 transactions after we detect change 127 | // to a creatorATA account. It returns instruction pair containing tx data, along with 128 | // meta, so we can fetch innerinstructions for the tx 129 | func (b *Bot) fetchCreatorATATrans(coin *Coin) ([]instPair, error) { 130 | var instPairs []instPair 131 | 132 | ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*900) 133 | defer cancel() 134 | 135 | latestTransResps, err := b.fetchNLastTrans(3, coin.creatorATA.String(), ctx) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | for _, resp := range latestTransResps { 141 | var transResult *rpc.GetTransactionResult = &rpc.GetTransactionResult{} 142 | if err := resp.GetObject(&transResult); err != nil { 143 | continue 144 | } 145 | 146 | if transResult == nil || transResult.Transaction == nil { 147 | continue 148 | } 149 | 150 | tx, err := transResult.Transaction.GetTransaction() 151 | if err != nil { 152 | continue 153 | } 154 | 155 | meta := transResult.Meta 156 | instPairs = append(instPairs, instPair{tx: tx, meta: meta}) 157 | } 158 | 159 | return instPairs, nil 160 | } 161 | 162 | func (b *Bot) isSellOrTransfer(instPairs []instPair, coin *Coin) bool { 163 | // immediately check for a sell 164 | for _, instPair := range instPairs { 165 | if detectTransfer(instPair, coin) { 166 | return true 167 | } 168 | } 169 | 170 | return detectSell(instPairs) 171 | } 172 | 173 | // detectSell uses the instruction pairs from the creator ATA detected tx 174 | // to see if a sell was detected in those instructions 175 | func detectSell(instPairs []instPair) bool { 176 | for _, instPair := range instPairs { 177 | for _, instruction := range instPair.tx.Message.Instructions { 178 | // Find the accounts of this instruction: 179 | accounts, err := instruction.ResolveInstructionAccounts(&instPair.tx.Message) 180 | if err != nil { 181 | continue 182 | } 183 | 184 | instr, err := pump.DecodeInstruction(accounts, instruction.Data) 185 | if err != nil { 186 | continue 187 | } 188 | 189 | data, err := instr.Data() 190 | if err != nil || len(data) < 8 { 191 | continue 192 | } 193 | 194 | typeID := data[0:8] 195 | 196 | for k, v := range pumpIDs { 197 | if k.Equal(typeID) { 198 | switch v.name { 199 | case "sell": 200 | fmt.Println("*** Found a sell in the decodedInstructions") 201 | return true 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | return false 209 | } 210 | 211 | func detectTransfer(pair instPair, coin *Coin) bool { 212 | if pair.meta == nil || len(pair.meta.InnerInstructions) == 0 { 213 | return false 214 | } 215 | 216 | for _, inst := range pair.meta.InnerInstructions { 217 | for _, innerInst := range inst.Instructions { 218 | progKey, err := pair.tx.ResolveProgramIDIndex(innerInst.ProgramIDIndex) 219 | if err != nil { 220 | continue 221 | } 222 | 223 | if !progKey.Equals(token.ProgramID) { 224 | continue 225 | } 226 | 227 | accounts, err := innerInst.ResolveInstructionAccounts(&pair.tx.Message) 228 | if err != nil { 229 | continue 230 | } 231 | 232 | decodedInstruction, err := token.DecodeInstruction(accounts, innerInst.Data) 233 | if err != nil { 234 | continue 235 | } 236 | 237 | // TODO: See if this is actually necessary. Would burn appear as transfer? 238 | // if _, ok := decodedInstruction.Impl.(*token.Burn); ok { 239 | // fmt.Println("User burned tokens") 240 | // return false 241 | // } 242 | 243 | // Check for a transfer instruction 244 | if transferInst, ok := decodedInstruction.Impl.(*token.Transfer); ok { 245 | sender := transferInst.GetSourceAccount().PublicKey.String() 246 | if sender == coin.creatorATA.String() { 247 | return true 248 | } 249 | } 250 | 251 | } 252 | } 253 | 254 | return false 255 | } 256 | -------------------------------------------------------------------------------- /handle-sell-coin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // HandleSellCoins iterates through our list of coins we've purchased, 9 | // or intend to purchase, checks if they are stale (already sold / buy tx failed), 10 | // or if they need to be sold, and handles both of those cases 11 | func (b *Bot) HandleSellCoins() { 12 | for { 13 | coinsToSell := b.fetchCoinsToSell() 14 | 15 | for _, coin := range coinsToSell { 16 | go b.SellCoinFast(coin) 17 | } 18 | 19 | // check for coins we should sell each 100 ms 20 | time.Sleep(100 * time.Millisecond) 21 | } 22 | } 23 | 24 | // fetchCoinsToSell returns coins we should sell, 25 | // but also deletes coins we no longer need to track 26 | func (b *Bot) fetchCoinsToSell() []*Coin { 27 | var coinsToSell []*Coin 28 | 29 | b.pendingCoinsLock.Lock() 30 | defer b.pendingCoinsLock.Unlock() 31 | 32 | for mintAddr, coin := range b.pendingCoins { 33 | if coin == nil { 34 | continue 35 | } 36 | 37 | // if we exited BuyCoin & do not hold tokens, remove this coin 38 | if coin.exitedBuyCoin && !coin.botHoldsTokens() { 39 | fmt.Println("Deleting", coin.mintAddr.String(), "because exited buy but no hold") 40 | delete(b.pendingCoins, mintAddr) 41 | } 42 | 43 | // sold coins and stopped listening to creator, delete coin 44 | if coin.exitedSellCoin && coin.exitedCreatorListener { 45 | fmt.Println("Deleting", coin.mintAddr.String(), "because exited creator listener and sellCoins routine") 46 | delete(b.pendingCoins, mintAddr) 47 | } 48 | 49 | // we hold tokens & creator sold, must exit 50 | // make sure we are not already selling this coin 51 | if coin.botHoldsTokens() && coin.creatorSold && !coin.isSellingCoin { 52 | b.status(fmt.Sprintf("Selling %s: (decision=creator sold)", coin.mintAddr.String())) 53 | coinsToSell = append(coinsToSell, coin) 54 | } 55 | } 56 | 57 | return coinsToSell 58 | } 59 | -------------------------------------------------------------------------------- /hash.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/gagliardetto/solana-go/rpc" 8 | ) 9 | 10 | func (b *Bot) fetchBlockhashLoop() { 11 | go func() { 12 | for { 13 | err := b.fetchLatestBlockhash() 14 | if err != nil { 15 | b.statusr(err) 16 | continue 17 | } 18 | 19 | time.Sleep(400 * time.Millisecond) 20 | } 21 | }() 22 | } 23 | 24 | func (b *Bot) fetchLatestBlockhash() error { 25 | recent, err := b.rpcClient.GetLatestBlockhash(context.TODO(), rpc.CommitmentFinalized) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | b.blockhash = &recent.Value.Blockhash 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /images/bot-running.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1fge/pump-fun-sniper-bot/58d048de34124079eaa8cfda6e4b8c1787b76655/images/bot-running.jpg -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/joho/godotenv" 10 | ) 11 | 12 | var ( 13 | // set up to be run on same machine as dedicated RPC 14 | // can be swapped out to separate RPC url 15 | rpcURL = "http://127.0.0.1:8799" 16 | wsURL = "ws://127.0.0.1:8800" 17 | proxyURL = "" 18 | 19 | sendTxRPCs = []string{ 20 | // insert public RPCs / alernate RPCs here to increase likelihood of tx landing 21 | } 22 | 23 | shouldProxy = strings.Contains(os.Getenv("PROXY_URL"), "http") 24 | ) 25 | 26 | func loadPrivateKey() (string, error) { 27 | if err := godotenv.Load(); err != nil { 28 | return "", err 29 | } 30 | 31 | return os.Getenv("PRIVATE_KEY"), nil 32 | } 33 | 34 | func main() { 35 | db, err := sql.Open("mysql", "root:XXXXXX!@/CoinTrades") 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | defer db.Close() 40 | 41 | privateKey, err := loadPrivateKey() 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | proxyURL = os.Getenv("PROXY_URL") 47 | 48 | // purchase coins with 0.05 solana, priority fee of 200000 microlamp 49 | bot, err := NewBot(rpcURL, wsURL, privateKey, db, 0.05, 200000) 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | bot.skipATALookup = true 55 | 56 | go bot.HandleNewMints() 57 | go bot.HandleBuyCoins() 58 | go bot.HandleSellCoins() 59 | 60 | if err := bot.beginJito(); err != nil { 61 | log.Fatal("Error Starting Jito", err) 62 | } 63 | 64 | select {} 65 | } 66 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/blockengine_client/blockengine.go: -------------------------------------------------------------------------------- 1 | package blockengine_client 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | 7 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 8 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 9 | "github.com/gagliardetto/solana-go" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/credentials" 12 | ) 13 | 14 | func NewRelayer(ctx context.Context, grpcDialURL string, privateKey solana.PrivateKey, tlsConfig *tls.Config, opts ...grpc.DialOption) (*Relayer, error) { 15 | if tlsConfig != nil { 16 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) 17 | } else { 18 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) 19 | } 20 | 21 | chErr := make(chan error) 22 | conn, err := pkg.CreateAndObserveGRPCConn(ctx, chErr, grpcDialURL, opts...) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | blockEngineRelayerClient := proto.NewBlockEngineRelayerClient(conn) 28 | authService := pkg.NewAuthenticationService(conn, privateKey) 29 | if err = authService.AuthenticateAndRefresh(proto.Role_RELAYER); err != nil { 30 | return nil, err 31 | } 32 | 33 | return &Relayer{ 34 | GrpcConn: conn, 35 | Client: blockEngineRelayerClient, 36 | Auth: pkg.NewAuthenticationService(conn, privateKey), 37 | ErrChan: chErr, 38 | }, nil 39 | } 40 | 41 | func NewValidator(ctx context.Context, grpcDialURL string, privateKey solana.PrivateKey, tlsConfig *tls.Config, opts ...grpc.DialOption) (*Validator, error) { 42 | if tlsConfig != nil { 43 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) 44 | } else { 45 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) 46 | } 47 | 48 | chErr := make(chan error) 49 | conn, err := pkg.CreateAndObserveGRPCConn(ctx, chErr, grpcDialURL, opts...) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | blockEngineValidatorClient := proto.NewBlockEngineValidatorClient(conn) 55 | authService := pkg.NewAuthenticationService(conn, privateKey) 56 | if err = authService.AuthenticateAndRefresh(proto.Role_VALIDATOR); err != nil { 57 | return nil, err 58 | } 59 | 60 | return &Validator{ 61 | GrpcConn: conn, 62 | Client: blockEngineValidatorClient, 63 | Auth: authService, 64 | ErrChan: chErr, 65 | }, nil 66 | } 67 | 68 | func (c *Validator) SubscribePackets() (proto.BlockEngineValidator_SubscribePacketsClient, error) { 69 | return c.Client.SubscribePackets(c.Auth.GrpcCtx, &proto.SubscribePacketsRequest{}) 70 | } 71 | 72 | // OnPacketSubscription is a wrapper of SubscribePackets. 73 | func (c *Validator) OnPacketSubscription(ctx context.Context) (<-chan *proto.SubscribePacketsResponse, <-chan error, error) { 74 | sub, err := c.SubscribePackets() 75 | if err != nil { 76 | return nil, nil, err 77 | } 78 | 79 | chPackets := make(chan *proto.SubscribePacketsResponse) 80 | chErr := make(chan error) 81 | 82 | go func() { 83 | for { 84 | select { 85 | case <-ctx.Done(): 86 | return 87 | default: 88 | resp, err := sub.Recv() 89 | if err != nil { 90 | chErr <- err 91 | continue 92 | } 93 | 94 | chPackets <- resp 95 | } 96 | } 97 | }() 98 | 99 | return chPackets, chErr, nil 100 | } 101 | 102 | func (c *Validator) SubscribeBundles() (proto.BlockEngineValidator_SubscribeBundlesClient, error) { 103 | return c.Client.SubscribeBundles(c.Auth.GrpcCtx, &proto.SubscribeBundlesRequest{}) 104 | } 105 | 106 | // OnBundleSubscription is a wrapper of SubscribeBundles. 107 | func (c *Validator) OnBundleSubscription(ctx context.Context) (<-chan []*proto.BundleUuid, <-chan error, error) { 108 | sub, err := c.SubscribeBundles() 109 | if err != nil { 110 | return nil, nil, err 111 | } 112 | 113 | chBundleUuid := make(chan []*proto.BundleUuid) 114 | chErr := make(chan error) 115 | 116 | go func() { 117 | for { 118 | select { 119 | case <-ctx.Done(): 120 | return 121 | case <-c.Auth.GrpcCtx.Done(): 122 | return 123 | default: 124 | resp, err := sub.Recv() 125 | if err != nil { 126 | chErr <- err 127 | continue 128 | } 129 | 130 | chBundleUuid <- resp.Bundles 131 | } 132 | } 133 | }() 134 | 135 | return chBundleUuid, chErr, nil 136 | } 137 | 138 | func (c *Validator) GetBlockBuilderFeeInfo(opts ...grpc.CallOption) (*proto.BlockBuilderFeeInfoResponse, error) { 139 | return c.Client.GetBlockBuilderFeeInfo(c.Auth.GrpcCtx, &proto.BlockBuilderFeeInfoRequest{}, opts...) 140 | } 141 | 142 | func (c *Relayer) SubscribeAccountsOfInterest(opts ...grpc.CallOption) (proto.BlockEngineRelayer_SubscribeAccountsOfInterestClient, error) { 143 | return c.Client.SubscribeAccountsOfInterest(c.Auth.GrpcCtx, &proto.AccountsOfInterestRequest{}, opts...) 144 | } 145 | 146 | // OnSubscribeAccountsOfInterest is a wrapper of SubscribeAccountsOfInterest. 147 | func (c *Relayer) OnSubscribeAccountsOfInterest(ctx context.Context) (<-chan *proto.AccountsOfInterestUpdate, <-chan error, error) { 148 | sub, err := c.SubscribeAccountsOfInterest() 149 | if err != nil { 150 | return nil, nil, err 151 | } 152 | 153 | chAccountOfInterest := make(chan *proto.AccountsOfInterestUpdate) 154 | chErr := make(chan error) 155 | 156 | go func() { 157 | for { 158 | select { 159 | case <-ctx.Done(): 160 | return 161 | case <-c.Auth.GrpcCtx.Done(): 162 | return 163 | default: 164 | resp, err := sub.Recv() 165 | if err != nil { 166 | chErr <- err 167 | continue 168 | } 169 | 170 | chAccountOfInterest <- resp 171 | } 172 | } 173 | }() 174 | 175 | return chAccountOfInterest, chErr, nil 176 | } 177 | 178 | func (c *Relayer) SubscribeProgramsOfInterest(opts ...grpc.CallOption) (proto.BlockEngineRelayer_SubscribeProgramsOfInterestClient, error) { 179 | return c.Client.SubscribeProgramsOfInterest(c.Auth.GrpcCtx, &proto.ProgramsOfInterestRequest{}, opts...) 180 | } 181 | 182 | // OnSubscribeProgramsOfInterest is a wrapper of SubscribeProgramsOfInterest. 183 | func (c *Relayer) OnSubscribeProgramsOfInterest(ctx context.Context) (<-chan *proto.ProgramsOfInterestUpdate, <-chan error, error) { 184 | sub, err := c.SubscribeProgramsOfInterest() 185 | if err != nil { 186 | return nil, nil, err 187 | } 188 | 189 | chProgramsOfInterest := make(chan *proto.ProgramsOfInterestUpdate) 190 | chErr := make(chan error) 191 | 192 | go func() { 193 | for { 194 | select { 195 | case <-ctx.Done(): 196 | return 197 | default: 198 | subInfo, err := sub.Recv() 199 | if err != nil { 200 | chErr <- err 201 | continue 202 | } 203 | 204 | chProgramsOfInterest <- subInfo 205 | } 206 | } 207 | }() 208 | 209 | return chProgramsOfInterest, chErr, nil 210 | } 211 | 212 | func (c *Relayer) StartExpiringPacketStream(opts ...grpc.CallOption) (proto.BlockEngineRelayer_StartExpiringPacketStreamClient, error) { 213 | return c.Client.StartExpiringPacketStream(c.Auth.GrpcCtx, opts...) 214 | } 215 | 216 | // OnStartExpiringPacketStream is a wrapper of StartExpiringPacketStream. 217 | func (c *Relayer) OnStartExpiringPacketStream(ctx context.Context) (<-chan *proto.StartExpiringPacketStreamResponse, <-chan error, error) { 218 | sub, err := c.StartExpiringPacketStream() 219 | if err != nil { 220 | return nil, nil, err 221 | } 222 | 223 | chPacket := make(chan *proto.StartExpiringPacketStreamResponse) 224 | chErr := make(chan error) 225 | 226 | go func() { 227 | for { 228 | select { 229 | case <-ctx.Done(): 230 | return 231 | case <-c.Auth.GrpcCtx.Done(): 232 | return 233 | default: 234 | resp, err := sub.Recv() 235 | if err != nil { 236 | chErr <- err 237 | continue 238 | } 239 | 240 | chPacket <- resp 241 | } 242 | } 243 | }() 244 | 245 | return chPacket, chErr, nil 246 | } 247 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/blockengine_client/blockengine_test.go: -------------------------------------------------------------------------------- 1 | package blockengine_client 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "testing" 9 | "time" 10 | 11 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 12 | "github.com/gagliardetto/solana-go" 13 | "github.com/joho/godotenv" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | // todo add more tests 18 | 19 | func TestMain(m *testing.M) { 20 | _, filename, _, _ := runtime.Caller(0) 21 | godotenv.Load(filepath.Join(filepath.Dir(filename), "..", "..", "..", "jito-go", ".env")) 22 | os.Exit(m.Run()) 23 | } 24 | 25 | func Test_BlockEngineRelayerClient(t *testing.T) { 26 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 27 | defer cancel() 28 | 29 | privKey, ok := os.LookupEnv("PRIVATE_KEY") 30 | if !assert.True(t, ok, "getting PRIVATE_KEY from .env") { 31 | t.FailNow() 32 | } 33 | 34 | if !assert.NotEqualf(t, "", privKey, "PRIVATE_KEY shouldn't be equal to [%s]", privKey) { 35 | t.FailNow() 36 | } 37 | 38 | privateKey, err := solana.PrivateKeyFromBase58(privKey) 39 | if !assert.NoError(t, err) { 40 | t.FailNow() 41 | } 42 | 43 | validator, err := NewValidator( 44 | ctx, 45 | jito_go.Amsterdam.BlockEngineURL, 46 | privateKey, 47 | nil, 48 | ) 49 | if !assert.NoError(t, err) { 50 | t.FailNow() 51 | } 52 | defer validator.GrpcConn.Close() 53 | 54 | t.Run("Validator_SubscribePackets", func(t *testing.T) { 55 | var sub proto.BlockEngineValidator_SubscribePacketsClient 56 | sub, err = validator.SubscribePackets() 57 | if !assert.NoError(t, err) { 58 | t.FailNow() 59 | } 60 | 61 | var resp *proto.SubscribePacketsResponse 62 | resp, err = sub.Recv() 63 | assert.NoError(t, err) 64 | 65 | assert.NotNil(t, resp.Batch) 66 | }) 67 | 68 | t.Run("Validator_SubscribeBundles", func(t *testing.T) { 69 | var sub proto.BlockEngineValidator_SubscribeBundlesClient 70 | sub, err = validator.SubscribeBundles() 71 | if !assert.NoError(t, err) { 72 | t.FailNow() 73 | } 74 | 75 | var resp *proto.SubscribeBundlesResponse 76 | resp, err = sub.Recv() 77 | if !assert.NoError(t, err) { 78 | t.FailNow() 79 | } 80 | 81 | for _, bundle := range resp.Bundles { 82 | assert.NotNil(t, bundle.Bundle) 83 | } 84 | }) 85 | 86 | t.Run("Validator_GetBlockBuilderFeeInfo", func(t *testing.T) { 87 | var resp *proto.BlockBuilderFeeInfoResponse 88 | resp, err = validator.GetBlockBuilderFeeInfo() 89 | if !assert.NoError(t, err) { 90 | t.FailNow() 91 | } 92 | 93 | resp.String() 94 | }) 95 | } 96 | 97 | func Test_BlockEngineValidatorClient(t *testing.T) { 98 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 99 | defer cancel() 100 | 101 | privKey, ok := os.LookupEnv("PRIVATE_KEY") 102 | if !assert.True(t, ok, "getting PRIVATE_KEY from .env") { 103 | t.FailNow() 104 | } 105 | 106 | privateKey, err := solana.PrivateKeyFromBase58(privKey) 107 | if !assert.NoError(t, err) { 108 | t.FailNow() 109 | } 110 | 111 | relayer, err := NewRelayer( 112 | ctx, 113 | jito_go.NewYork.RelayerURL, 114 | privateKey, 115 | nil, 116 | ) 117 | if !assert.NoError(t, err) { 118 | t.FailNow() 119 | } 120 | defer relayer.GrpcConn.Close() 121 | 122 | t.Run("SubscribeAccountsOfInterest", func(t *testing.T) { 123 | var sub proto.BlockEngineRelayer_SubscribeAccountsOfInterestClient 124 | sub, err = relayer.SubscribeAccountsOfInterest() 125 | if !assert.NoError(t, err) { 126 | t.FailNow() 127 | } 128 | 129 | var resp *proto.AccountsOfInterestUpdate 130 | resp, err = sub.Recv() 131 | if !assert.NoError(t, err) { 132 | t.FailNow() 133 | } 134 | 135 | for _, account := range resp.Accounts { 136 | assert.NotEqual(t, "", account) 137 | } 138 | }) 139 | 140 | t.Run("StartExpiringPacketStream", func(t *testing.T) { 141 | var sub proto.BlockEngineRelayer_StartExpiringPacketStreamClient 142 | sub, err = relayer.StartExpiringPacketStream() 143 | if !assert.NoError(t, err) { 144 | t.FailNow() 145 | } 146 | 147 | var resp *proto.StartExpiringPacketStreamResponse 148 | resp, err = sub.Recv() 149 | if !assert.NoError(t, err) { 150 | t.FailNow() 151 | } 152 | 153 | assert.NotNil(t, resp.Heartbeat) 154 | }) 155 | 156 | t.Run("SubscribeProgramsOfInterest", func(t *testing.T) { 157 | sub, err := relayer.SubscribeProgramsOfInterest() 158 | if !assert.NoError(t, err) { 159 | t.FailNow() 160 | } 161 | 162 | resp, err := sub.Recv() 163 | if !assert.NoError(t, err) { 164 | t.FailNow() 165 | } 166 | 167 | for _, program := range resp.Programs { 168 | assert.NotEqual(t, "", program) 169 | } 170 | }) 171 | } 172 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/blockengine_client/types.go: -------------------------------------------------------------------------------- 1 | package blockengine_client 2 | 3 | import ( 4 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 5 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | type Relayer struct { 10 | GrpcConn *grpc.ClientConn 11 | 12 | Client proto.BlockEngineRelayerClient 13 | 14 | Auth *pkg.AuthenticationService 15 | 16 | ErrChan <-chan error // ErrChan is used for dispatching errors from functions executed within goroutines. 17 | } 18 | 19 | type Validator struct { 20 | GrpcConn *grpc.ClientConn 21 | 22 | Client proto.BlockEngineValidatorClient 23 | 24 | Auth *pkg.AuthenticationService 25 | 26 | ErrChan <-chan error // ErrChan is used for dispatching errors from functions executed within goroutines. 27 | } 28 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/geyser_client/geyser.go: -------------------------------------------------------------------------------- 1 | package geyser_client 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | 8 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 9 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/credentials" 12 | ) 13 | 14 | // New creates a new RPC client and connects to the provided endpoint. A Geyser RPC URL is required. 15 | func New(ctx context.Context, grpcDialURL string, tlsConfig *tls.Config, opts ...grpc.DialOption) (*Client, error) { 16 | if tlsConfig != nil { 17 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) 18 | } else { 19 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) 20 | } 21 | 22 | chErr := make(chan error) 23 | conn, err := pkg.CreateAndObserveGRPCConn(ctx, chErr, grpcDialURL, opts...) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | geyserClient := proto.NewGeyserClient(conn) 29 | 30 | return &Client{ 31 | GrpcConn: conn, 32 | Ctx: ctx, 33 | Geyser: geyserClient, 34 | ErrChan: chErr, 35 | }, nil 36 | } 37 | 38 | func (c *Client) SubscribePartialAccountUpdates(opts ...grpc.CallOption) (proto.Geyser_SubscribePartialAccountUpdatesClient, error) { 39 | return c.Geyser.SubscribePartialAccountUpdates(c.Ctx, &proto.SubscribePartialAccountUpdatesRequest{SkipVoteAccounts: true}, opts...) 40 | } 41 | 42 | // OnPartialAccountUpdates is a wrapper of SubscribePartialAccountUpdates. 43 | func (c *Client) OnPartialAccountUpdates(ctx context.Context) (<-chan *proto.PartialAccountUpdate, <-chan error, error) { 44 | sub, err := c.SubscribePartialAccountUpdates() 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | 49 | ch := make(chan *proto.PartialAccountUpdate) 50 | chErr := make(chan error) 51 | 52 | go func() { 53 | for { 54 | select { 55 | case <-ctx.Done(): 56 | return 57 | case <-c.Ctx.Done(): 58 | return 59 | default: 60 | subInfo, err := sub.Recv() 61 | if err != nil { 62 | chErr <- fmt.Errorf("error OnPartialAccountUpdates: %w", err) 63 | continue 64 | } 65 | 66 | ch <- subInfo.GetPartialAccountUpdate() 67 | } 68 | } 69 | }() 70 | 71 | return ch, chErr, nil 72 | } 73 | 74 | func (c *Client) SubscribeBlockUpdates(opts ...grpc.CallOption) (proto.Geyser_SubscribeBlockUpdatesClient, error) { 75 | return c.Geyser.SubscribeBlockUpdates(c.Ctx, &proto.SubscribeBlockUpdatesRequest{}, opts...) 76 | } 77 | 78 | // OnBlockUpdates is a wrapper of SubscribeBlockUpdates. 79 | func (c *Client) OnBlockUpdates(ctx context.Context) (<-chan *proto.TimestampedBlockUpdate, <-chan error, error) { 80 | sub, err := c.SubscribeBlockUpdates() 81 | if err != nil { 82 | return nil, nil, err 83 | } 84 | 85 | chBlock := make(chan *proto.TimestampedBlockUpdate) 86 | chErr := make(chan error) 87 | 88 | go func() { 89 | for { 90 | select { 91 | case <-ctx.Done(): 92 | return 93 | case <-c.Ctx.Done(): 94 | return 95 | default: 96 | resp, err := sub.Recv() 97 | if err != nil { 98 | chErr <- fmt.Errorf("error OnBlockUpdates: %w", err) 99 | continue 100 | } 101 | 102 | chBlock <- resp 103 | } 104 | } 105 | }() 106 | 107 | return chBlock, chErr, nil 108 | } 109 | 110 | func (c *Client) SubscribeAccountUpdates(accounts []string, opts ...grpc.CallOption) (proto.Geyser_SubscribeAccountUpdatesClient, error) { 111 | return c.Geyser.SubscribeAccountUpdates(c.Ctx, &proto.SubscribeAccountUpdatesRequest{Accounts: pkg.StrSliceToByteSlice(accounts)}, opts...) 112 | } 113 | 114 | // OnAccountUpdates is a wrapper of SubscribeAccountUpdates. 115 | func (c *Client) OnAccountUpdates(ctx context.Context, accounts []string, opts ...grpc.CallOption) (<-chan *proto.TimestampedAccountUpdate, <-chan error, error) { 116 | sub, err := c.SubscribeAccountUpdates(accounts, opts...) 117 | if err != nil { 118 | return nil, nil, err 119 | } 120 | 121 | chAccount := make(chan *proto.TimestampedAccountUpdate) 122 | chErr := make(chan error) 123 | 124 | go func() { 125 | for { 126 | select { 127 | case <-ctx.Done(): 128 | return 129 | case <-c.Ctx.Done(): 130 | return 131 | default: 132 | resp, err := sub.Recv() 133 | if err != nil { 134 | chErr <- fmt.Errorf("error OnAccountUpdates: %w", err) 135 | continue 136 | } 137 | 138 | chAccount <- resp 139 | } 140 | } 141 | }() 142 | 143 | return chAccount, chErr, nil 144 | } 145 | 146 | func (c *Client) SubscribeProgramUpdates(programs []string, opts ...grpc.CallOption) (proto.Geyser_SubscribeProgramUpdatesClient, error) { 147 | return c.Geyser.SubscribeProgramUpdates(c.Ctx, &proto.SubscribeProgramsUpdatesRequest{Programs: pkg.StrSliceToByteSlice(programs)}, opts...) 148 | } 149 | 150 | // OnProgramUpdates is a wrapper of SubscribeProgramUpdates. 151 | func (c *Client) OnProgramUpdates(ctx context.Context, programs []string, opts ...grpc.CallOption) (<-chan *proto.TimestampedAccountUpdate, <-chan error, error) { 152 | sub, err := c.SubscribeProgramUpdates(programs, opts...) 153 | if err != nil { 154 | return nil, nil, err 155 | } 156 | 157 | chProgram := make(chan *proto.TimestampedAccountUpdate) 158 | chErr := make(chan error) 159 | 160 | go func() { 161 | for { 162 | select { 163 | case <-ctx.Done(): 164 | return 165 | case <-c.Ctx.Done(): 166 | return 167 | default: 168 | resp, err := sub.Recv() 169 | if err != nil { 170 | chErr <- fmt.Errorf("error OnProgramUpdate: %w", err) 171 | continue 172 | } 173 | 174 | chProgram <- resp 175 | } 176 | } 177 | }() 178 | 179 | return chProgram, chErr, nil 180 | } 181 | 182 | func (c *Client) SubscribeTransactionUpdates(opts ...grpc.CallOption) (proto.Geyser_SubscribeTransactionUpdatesClient, error) { 183 | return c.Geyser.SubscribeTransactionUpdates(c.Ctx, &proto.SubscribeTransactionUpdatesRequest{}, opts...) 184 | } 185 | 186 | // OnTransactionUpdates is a wrapper of SubscribeTransactionUpdates. 187 | func (c *Client) OnTransactionUpdates(ctx context.Context) (<-chan *proto.TimestampedTransactionUpdate, <-chan error, error) { 188 | sub, err := c.SubscribeTransactionUpdates() 189 | if err != nil { 190 | return nil, nil, err 191 | } 192 | 193 | chTx := make(chan *proto.TimestampedTransactionUpdate) 194 | chErr := make(chan error) 195 | 196 | go func() { 197 | for { 198 | select { 199 | case <-ctx.Done(): 200 | return 201 | case <-c.Ctx.Done(): 202 | return 203 | default: 204 | resp, err := sub.Recv() 205 | if err != nil { 206 | chErr <- fmt.Errorf("error OnTransactionUpdates: %w", err) 207 | continue 208 | } 209 | 210 | chTx <- resp 211 | } 212 | } 213 | }() 214 | 215 | return chTx, chErr, err 216 | } 217 | 218 | func (c *Client) SubscribeSlotUpdates(opts ...grpc.CallOption) (proto.Geyser_SubscribeSlotUpdatesClient, error) { 219 | return c.Geyser.SubscribeSlotUpdates(c.Ctx, &proto.SubscribeSlotUpdateRequest{}, opts...) 220 | } 221 | 222 | // OnSlotUpdates is a wrapper of SubscribeSlotUpdates. 223 | func (c *Client) OnSlotUpdates(ctx context.Context, opts ...grpc.CallOption) (<-chan *proto.TimestampedSlotUpdate, <-chan error, error) { 224 | sub, err := c.SubscribeSlotUpdates(opts...) 225 | if err != nil { 226 | return nil, nil, err 227 | } 228 | 229 | chSlot := make(chan *proto.TimestampedSlotUpdate) 230 | chErr := make(chan error) 231 | 232 | go func() { 233 | for { 234 | select { 235 | case <-ctx.Done(): 236 | return 237 | case <-c.Ctx.Done(): 238 | return 239 | default: 240 | resp, err := sub.Recv() 241 | if err != nil { 242 | chErr <- fmt.Errorf("error OnSlotUpdates: %w", err) 243 | continue 244 | } 245 | 246 | chSlot <- resp 247 | } 248 | } 249 | }() 250 | 251 | return chSlot, chErr, nil 252 | } 253 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/geyser_client/geyser_test.go: -------------------------------------------------------------------------------- 1 | package geyser_client 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "testing" 9 | "time" 10 | 11 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 12 | "github.com/joho/godotenv" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestMain(m *testing.M) { 17 | _, filename, _, _ := runtime.Caller(0) 18 | godotenv.Load(filepath.Join(filepath.Dir(filename), "..", "..", "..", "jito-go", ".env")) 19 | os.Exit(m.Run()) 20 | } 21 | 22 | func Test_GeyserClient(t *testing.T) { 23 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 24 | defer cancel() 25 | 26 | rpcAddr, ok := os.LookupEnv("GEYSER_RPC") 27 | if !assert.True(t, ok, "getting GEYSER_RPC from .env") { 28 | t.FailNow() 29 | } 30 | 31 | if !assert.NotEqualf(t, "", rpcAddr, "GEYSER_RPC shouldn't be equal to [%s]", rpcAddr) { 32 | t.FailNow() 33 | } 34 | 35 | client, err := New( 36 | ctx, 37 | rpcAddr, 38 | nil, 39 | ) 40 | if !assert.NoError(t, err) { 41 | t.FailNow() 42 | } 43 | defer client.GrpcConn.Close() 44 | 45 | // ion have a Geyser RPC although USDC program should work for both, if not lmk :) 46 | accounts := []string{"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"} 47 | programs := []string{"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"} 48 | 49 | t.Run("SubscribeBlockUpdates", func(t *testing.T) { 50 | sub, _, err := client.OnBlockUpdates(ctx) 51 | if !assert.NoError(t, err) { 52 | t.FailNow() 53 | } 54 | 55 | block := <-sub 56 | assert.NotNil(t, block.BlockUpdate.BlockHeight) 57 | }) 58 | 59 | t.Run("SubscribePartialAccountUpdates", func(t *testing.T) { 60 | var sub proto.Geyser_SubscribePartialAccountUpdatesClient 61 | sub, err = client.SubscribePartialAccountUpdates() 62 | if !assert.NoError(t, err) { 63 | t.FailNow() 64 | } 65 | 66 | var resp *proto.MaybePartialAccountUpdate 67 | resp, err = sub.Recv() 68 | assert.NoError(t, err) 69 | 70 | assert.NotNil(t, resp.GetHb()) 71 | }) 72 | 73 | t.Run("SubscribeAccountUpdates", func(t *testing.T) { 74 | var sub proto.Geyser_SubscribeAccountUpdatesClient 75 | sub, err = client.SubscribeAccountUpdates(accounts) 76 | if !assert.NoError(t, err) { 77 | t.FailNow() 78 | } 79 | 80 | var resp *proto.TimestampedAccountUpdate 81 | resp, err = sub.Recv() 82 | assert.NoError(t, err) 83 | 84 | assert.NotNil(t, resp.Ts) 85 | assert.NotNil(t, resp.AccountUpdate.TxSignature) 86 | }) 87 | 88 | t.Run("SubscribeProgramUpdates", func(t *testing.T) { 89 | var sub proto.Geyser_SubscribeProgramUpdatesClient 90 | sub, err = client.SubscribeProgramUpdates(programs) 91 | if !assert.NoError(t, err) { 92 | t.FailNow() 93 | } 94 | 95 | var resp *proto.TimestampedAccountUpdate 96 | resp, err = sub.Recv() 97 | assert.NoError(t, err) 98 | 99 | assert.NotNil(t, resp.Ts) 100 | assert.NotNil(t, resp.AccountUpdate.TxSignature) 101 | }) 102 | 103 | t.Run("SubscribeTransactionUpdates", func(t *testing.T) { 104 | var sub proto.Geyser_SubscribeTransactionUpdatesClient 105 | sub, err = client.SubscribeTransactionUpdates() 106 | if !assert.NoError(t, err) { 107 | t.FailNow() 108 | } 109 | 110 | var resp *proto.TimestampedTransactionUpdate 111 | resp, err = sub.Recv() 112 | assert.NoError(t, err) 113 | 114 | assert.NotNil(t, resp.Ts) 115 | assert.NotNil(t, resp.Transaction) 116 | }) 117 | 118 | t.Run("SubscribeSlotUpdates", func(t *testing.T) { 119 | var sub proto.Geyser_SubscribeSlotUpdatesClient 120 | sub, err = client.SubscribeSlotUpdates() 121 | if !assert.NoError(t, err) { 122 | t.FailNow() 123 | } 124 | 125 | var resp *proto.TimestampedSlotUpdate 126 | resp, err = sub.Recv() 127 | assert.NotNil(t, err) 128 | 129 | assert.NotNil(t, resp.Ts) 130 | assert.NotNil(t, resp.SlotUpdate) 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/geyser_client/types.go: -------------------------------------------------------------------------------- 1 | package geyser_client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | type Client struct { 11 | GrpcConn *grpc.ClientConn 12 | Ctx context.Context 13 | 14 | Geyser proto.GeyserClient 15 | 16 | ErrChan <-chan error 17 | } 18 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/relayer_client/relayer.go: -------------------------------------------------------------------------------- 1 | package relayer_client 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | 8 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 9 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 10 | "github.com/gagliardetto/solana-go" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/credentials" 13 | ) 14 | 15 | func New(ctx context.Context, grpcDialURL string, privateKey solana.PrivateKey, tlsConfig *tls.Config, opts ...grpc.DialOption) (*Client, error) { 16 | if tlsConfig != nil { 17 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) 18 | } else { 19 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) 20 | } 21 | 22 | chErr := make(chan error) 23 | conn, err := pkg.CreateAndObserveGRPCConn(ctx, chErr, grpcDialURL, opts...) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | relayerClient := proto.NewRelayerClient(conn) 29 | authService := pkg.NewAuthenticationService(conn, privateKey) 30 | if err = authService.AuthenticateAndRefresh(proto.Role_RELAYER); err != nil { 31 | return nil, err 32 | } 33 | 34 | return &Client{ 35 | GrpcConn: conn, 36 | Relayer: relayerClient, 37 | Auth: authService, 38 | ErrChan: chErr, 39 | }, nil 40 | } 41 | 42 | func (c *Client) GetTpuConfigs(opts ...grpc.CallOption) (*proto.GetTpuConfigsResponse, error) { 43 | return c.Relayer.GetTpuConfigs(c.Auth.GrpcCtx, &proto.GetTpuConfigsRequest{}, opts...) 44 | } 45 | 46 | func (c *Client) NewPacketsSubscription(opts ...grpc.CallOption) (proto.Relayer_SubscribePacketsClient, error) { 47 | return c.Relayer.SubscribePackets(c.Auth.GrpcCtx, &proto.SubscribePacketsRequest{}, opts...) 48 | } 49 | 50 | // SubscribePackets is a wrapper around NewPacketsSubscription. 51 | func (c *Client) SubscribePackets(ctx context.Context) (<-chan []*solana.Transaction, <-chan error, error) { 52 | chTx := make(chan []*solana.Transaction) 53 | chErr := make(chan error) 54 | 55 | sub, err := c.NewPacketsSubscription() 56 | if err != nil { 57 | return nil, nil, err 58 | } 59 | 60 | go func() { 61 | for { 62 | select { 63 | case <-ctx.Done(): 64 | return 65 | case <-c.Auth.GrpcCtx.Done(): 66 | return 67 | default: 68 | var packet *proto.SubscribePacketsResponse 69 | packet, err = sub.Recv() 70 | if err != nil { 71 | chErr <- fmt.Errorf("SubscribePackets: failed to receive packet information: %w", err) 72 | } 73 | 74 | var txns = make([]*solana.Transaction, 0, len(packet.Batch.GetPackets())) 75 | txns, err = pkg.ConvertBatchProtobufPacketToTransaction(packet.Batch.GetPackets()) 76 | if err != nil { 77 | chErr <- fmt.Errorf("SubscribePackets: failed to convert protobuf packet to transaction: %w", err) 78 | } 79 | 80 | chTx <- txns 81 | } 82 | } 83 | }() 84 | 85 | return chTx, chErr, nil 86 | } 87 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/relayer_client/relayer_test.go: -------------------------------------------------------------------------------- 1 | package relayer_client 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "testing" 9 | "time" 10 | 11 | jito_go "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go" 12 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 13 | "github.com/gagliardetto/solana-go" 14 | "github.com/joho/godotenv" 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | _, filename, _, _ := runtime.Caller(0) 20 | godotenv.Load(filepath.Join(filepath.Dir(filename), "..", "..", "..", "jito-go", ".env")) 21 | os.Exit(m.Run()) 22 | } 23 | 24 | func Test_RelayerClient(t *testing.T) { 25 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 26 | defer cancel() 27 | 28 | privKey, ok := os.LookupEnv("PRIVATE_KEY") 29 | if !assert.True(t, ok, "getting PRIVATE_KEY from .env") { 30 | t.FailNow() 31 | } 32 | 33 | client, err := New( 34 | ctx, 35 | jito_go.Amsterdam.BlockEngineURL, 36 | solana.MustPrivateKeyFromBase58(privKey), 37 | nil, 38 | ) 39 | if !assert.NoError(t, err) { 40 | t.FailNow() 41 | } 42 | defer client.GrpcConn.Close() 43 | 44 | t.Run("GetTpuConfig", func(t *testing.T) { 45 | resp, err := client.GetTpuConfigs() 46 | if !assert.NoError(t, err) { 47 | t.FailNow() 48 | } 49 | 50 | if assert.NotEqual(t, "", resp.Tpu.Ip) { 51 | } 52 | }) 53 | 54 | t.Run("SubscribePacket", func(t *testing.T) { 55 | sub, err := client.NewPacketsSubscription() 56 | if !assert.NoError(t, err) { 57 | t.FailNow() 58 | } 59 | 60 | var recv *proto.SubscribePacketsResponse 61 | recv, err = sub.Recv() 62 | assert.NoError(t, err) 63 | 64 | recv.Header.String() 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/relayer_client/types.go: -------------------------------------------------------------------------------- 1 | package relayer_client 2 | 3 | import ( 4 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 5 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | type Client struct { 10 | GrpcConn *grpc.ClientConn 11 | 12 | Relayer proto.RelayerClient 13 | 14 | Auth *pkg.AuthenticationService 15 | 16 | ErrChan <-chan error // ErrChan is used for dispatching errors from functions executed within goroutines. 17 | } 18 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/searcher_client/searcher_test.go: -------------------------------------------------------------------------------- 1 | package searcher_client 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "testing" 10 | "time" 11 | 12 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 13 | "github.com/gagliardetto/solana-go" 14 | "github.com/gagliardetto/solana-go/programs/system" 15 | "github.com/gagliardetto/solana-go/rpc" 16 | "github.com/joho/godotenv" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | func TestMain(m *testing.M) { 21 | _, filename, _, _ := runtime.Caller(0) 22 | godotenv.Load(filepath.Join(filepath.Dir(filename), "..", "..", "..", "jito-go", ".env")) 23 | os.Exit(m.Run()) 24 | } 25 | 26 | func Test_SearcherClient(t *testing.T) { 27 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) 28 | defer cancel() 29 | 30 | privKey, ok := os.LookupEnv("PRIVATE_KEY") 31 | if !assert.True(t, ok, "getting PRIVATE_KEY from .env") { 32 | t.FailNow() 33 | } 34 | 35 | rpcAddr, ok := os.LookupEnv("JITO_RPC") 36 | if !assert.True(t, ok, "getting JITO_RPC from .env") { 37 | t.FailNow() 38 | } 39 | 40 | client, err := New( 41 | ctx, 42 | jito_go.NewYork.BlockEngineURL, 43 | rpc.New(rpcAddr), 44 | rpc.New(rpc.MainNetBeta_RPC), 45 | solana.MustPrivateKeyFromBase58(privKey), 46 | nil, 47 | ) 48 | if !assert.NoError(t, err) { 49 | t.FailNow() 50 | } 51 | defer client.GrpcConn.Close() 52 | 53 | httpClient := &http.Client{ 54 | Timeout: 10 * time.Second, 55 | } 56 | 57 | regions := []string{ 58 | jito_go.Amsterdam.Region, 59 | jito_go.NewYork.Region, 60 | jito_go.Frankfurt.Region, 61 | jito_go.Tokyo.Region, 62 | } 63 | 64 | bundles := []string{ 65 | "fc5f0b63c8a2e75193394311d7063e904ce4cf31a63ad6c5809277c1a68ec935", 66 | "d3925b4f9c6dc5112b89d07c854d55f6c52c2a528ff85a42bc3be9e91a80f290", 67 | "bdf8b0e5cad979ed71c40fe1a6b8d2589cc046d96fc43ff020a98c6b732b9ae9", 68 | "3821951e175b8186dfe0fbf05b15837edd1538659692721cea8c27368670859b", 69 | "e41a99b23554f26bc1c85552e027fe7ad95133c6a741a4920b6442f1e6f98ca7", 70 | "a20fc66cf0155f2c803d1f37bab54775a9110840e21d50369d8196886df97798", 71 | "52c0375ca1acee04d697faf2abe5d7e499856fcc15457673cba8c5e0673f398b", 72 | } 73 | 74 | t.Run("GetRegions", func(t *testing.T) { 75 | var resp *proto.GetRegionsResponse 76 | resp, err = client.GetRegions() 77 | assert.NoError(t, err) 78 | assert.Equal(t, jito_go.NewYork.Region, resp.CurrentRegion) 79 | }) 80 | 81 | t.Run("GetConnectedLeaders", func(t *testing.T) { 82 | _, err = client.GetConnectedLeaders() 83 | assert.NoError(t, err) 84 | }) 85 | 86 | t.Run("GetConnectedLeadersRegioned", func(t *testing.T) { 87 | _, err = client.GetConnectedLeadersRegioned(regions) 88 | assert.NoError(t, err) 89 | }) 90 | 91 | t.Run("GetTipAccounts", func(t *testing.T) { 92 | _, err = client.GetTipAccounts() 93 | assert.NoError(t, err) 94 | }) 95 | 96 | t.Run("GetNextScheduledLeader", func(t *testing.T) { 97 | _, err = client.GetNextScheduledLeader(regions) 98 | assert.NoError(t, err) 99 | }) 100 | 101 | t.Run("SubscribeMempoolAccount", func(t *testing.T) { 102 | t.Skip("skipping test due to rpc method being disabled") 103 | 104 | accounts := []string{ 105 | "GSE6vfr6vws493G22jfwCU6Zawh3dfvSYXYQqKhFsBwe", 106 | "xxxxxxqSkjrSvY1igNYjwcw5f9QskeLRKYEmJ1MezhB", 107 | "93WbteZH6nrWeHd5JT6mJE5VHbWcCTDZzzqqnpZ98V9G", 108 | "FSHZdx73rEGcS5JXUXvW6h8i4AtsrfPTHcgcbXLVUD3A", 109 | "Ez2U27TRScksd6q7xoVgX44gX9HAjviN2cdKAL3cFBFE", 110 | "EAHJNfFDtivTMzKMNXzwAF9RTAeTd4aEYVwLjCiQWY1E", 111 | "AeF3qRpn7DDuRjHhWmyqmZuZGguHXjsNYCzmNv2ZcuMQ", 112 | "4CX53LQNwFs3tyRFfwkMxsPV8daao1zCiGQjMMAkKSqx", 113 | "EePnRqV4Q2VEHp5nPADqeGKcfPMFmnhDW1Ln9LKsTzWQ", 114 | "6WkVGG2vaKcpgsf5dEYHunZRQHHjZWEUfkWiGxtBGnNg", 115 | "EMtQTumnZYnv7NSZNGr9WpSSMahkvmNeo9hjhbB9gqFR", 116 | "4SkEmhCEdLbJxKk6iFzCJ4eR1rLQGHRTs3q8i2PHLbq8", 117 | "HhuJCViqUewRNXrhwNuXCC7gqp2o1cUhx9a3nqEGkkqt", 118 | "364kNi4LbCh8iDuNvmbHbPML4N3xbg6msZnaj5dFSJbL", 119 | "nJMeypdLT1FfSkzNrdCZnVk5HXKiMNRcgCB9Hj554zu", 120 | "HGEj9nJHdAWJKMHGGHRnhvb3i1XakELSRTn5B4otmAhU", 121 | "DcpYXJsWBgkV6kck4a7cWBg6B4epPeFRCMZJjxudGKh4", 122 | "6nsC3UXTCpHq4tXZ1GEeVPg28B9NF8UV4G14dpm9WCUb", 123 | "B4WGtoLYuF4bF5QUjsnSLFYJVrRhNs8N2NqKqojxxKs8", 124 | "4sUfdLGg4txSZJdfTkihKLNbM7Xx8WyCmmNqmXc65fjY", 125 | } 126 | 127 | txCh, errCh, err := client.SubscribeAccountsMempoolTransactions(ctx, accounts, regions) 128 | if !assert.NoError(t, err) { 129 | t.FailNow() 130 | } 131 | 132 | for { 133 | select { 134 | case <-ctx.Done(): 135 | t.Fatal(ctx.Err()) 136 | case <-errCh: 137 | t.Fatal(err) 138 | default: 139 | tx := <-txCh 140 | assert.NotNil(t, tx) 141 | break 142 | } 143 | } 144 | }) 145 | 146 | t.Run("SubscribeMempoolProgram", func(t *testing.T) { 147 | t.Skip("skipping test due to rpc method being disabled") 148 | USDC := ("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") 149 | PENG := ("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8") 150 | 151 | programs := []string{USDC, PENG} 152 | 153 | txCh, errCh, err := client.SubscribeProgramsMempoolTransactions(ctx, programs, regions) 154 | if !assert.NoError(t, err) { 155 | t.FailNow() 156 | } 157 | 158 | for { 159 | select { 160 | case <-ctx.Done(): 161 | t.Fatal(ctx.Err()) 162 | case <-errCh: 163 | t.Fatal(err) 164 | default: 165 | tx := <-txCh 166 | assert.NotNil(t, tx) 167 | break 168 | } 169 | } 170 | }) 171 | 172 | t.Run("SimulateBundle", func(t *testing.T) { 173 | var pkey string 174 | pkey, ok = os.LookupEnv("PRIVATE_KEY_WITH_FUNDS") 175 | assert.True(t, ok, "getting PRIVATE_KEY_WITH_FUNDS from .env") 176 | 177 | var fundedWallet solana.PrivateKey 178 | fundedWallet, err = solana.PrivateKeyFromBase58(pkey) 179 | assert.NoError(t, err, "converting private key with funds to type solana.PrivateKey") 180 | 181 | var blockHash *rpc.GetRecentBlockhashResult 182 | var tx *solana.Transaction 183 | 184 | blockHash, err = client.RpcConn.GetRecentBlockhash(ctx, rpc.CommitmentConfirmed) 185 | if !assert.NoError(t, err, "getting recent block hash from RPC") { 186 | t.FailNow() 187 | } 188 | 189 | var tipInst solana.Instruction 190 | tipInst, err = client.GenerateTipRandomAccountInstruction(1000000, fundedWallet.PublicKey()) 191 | if !assert.NoError(t, err) { 192 | t.FailNow() 193 | } 194 | 195 | tx, err = solana.NewTransaction( 196 | []solana.Instruction{ 197 | system.NewTransferInstruction( 198 | 10000000, 199 | fundedWallet.PublicKey(), 200 | solana.MustPublicKeyFromBase58("A6njahNqC6qKde6YtbHdr1MZsB5KY9aKfzTY1cj8jU3v"), 201 | ).Build(), 202 | tipInst, 203 | }, 204 | blockHash.Value.Blockhash, 205 | solana.TransactionPayer(fundedWallet.PublicKey()), 206 | ) 207 | if !assert.NoError(t, err, "creating solana transaction") { 208 | t.FailNow() 209 | } 210 | 211 | _, err = tx.Sign( 212 | func(key solana.PublicKey) *solana.PrivateKey { 213 | if fundedWallet.PublicKey().Equals(key) { 214 | return &fundedWallet 215 | } 216 | return nil 217 | }, 218 | ) 219 | if !assert.NoError(t, err, "signing transaction") { 220 | t.FailNow() 221 | } 222 | 223 | _, err = client.SimulateBundle( 224 | ctx, 225 | SimulateBundleParams{ 226 | EncodedTransactions: []string{tx.MustToBase64()}, 227 | }, 228 | SimulateBundleConfig{ 229 | PreExecutionAccountsConfigs: []ExecutionAccounts{ 230 | { 231 | Encoding: "base64", 232 | Addresses: []string{"3vjULHsUbX4J2nXZJQQSHkTHoBqhedvHQPDNaAgT9dwG"}, 233 | }, 234 | }, 235 | PostExecutionAccountsConfigs: []ExecutionAccounts{ 236 | { 237 | Encoding: "base64", 238 | Addresses: []string{"3vjULHsUbX4J2nXZJQQSHkTHoBqhedvHQPDNaAgT9dwG"}, 239 | }, 240 | }, 241 | }, 242 | ) 243 | assert.NoError(t, err, "simulating bundle") 244 | }) 245 | 246 | t.Run("GetBundleStatuses_Client", func(t *testing.T) { 247 | _, err := client.GetBundleStatuses(ctx, []string{bundles[0]}) 248 | if !assert.NoError(t, err) { 249 | t.FailNow() 250 | } 251 | }) 252 | 253 | t.Run("BatchGetBundleStatuses_Client", func(t *testing.T) { 254 | _, err := client.BatchGetBundleStatuses(ctx, bundles...) 255 | if !assert.NoError(t, err) { 256 | t.FailNow() 257 | } 258 | }) 259 | 260 | t.Run("GetBundleStatuses_Http", func(t *testing.T) { 261 | _, err := GetBundleStatuses(httpClient, []string{bundles[0]}) 262 | if !assert.NoError(t, err) { 263 | t.FailNow() 264 | } 265 | }) 266 | 267 | t.Run("BatchGetBundleStatuses_Http", func(t *testing.T) { 268 | _, err := BatchGetBundleStatuses(httpClient, bundles...) 269 | if !assert.NoError(t, err) { 270 | t.FailNow() 271 | } 272 | }) 273 | } 274 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/searcher_client/types.go: -------------------------------------------------------------------------------- 1 | package searcher_client 2 | 3 | import ( 4 | "math/big" 5 | "net/url" 6 | 7 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 8 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 9 | "github.com/gagliardetto/solana-go/rpc" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | var jitoURL = &url.URL{ 14 | Scheme: "https", 15 | Host: "mainnet.block-engine.jito.wtf", 16 | Path: "/api/v1/bundles", 17 | } 18 | 19 | type Client struct { 20 | GrpcConn *grpc.ClientConn 21 | RpcConn *rpc.Client // Utilized for executing standard Solana RPC requests. 22 | JitoRpcConn *rpc.Client // Utilized for executing specific Jito RPC requests (Jito node required). 23 | 24 | SearcherService proto.SearcherServiceClient 25 | BundleStreamSubscription proto.SearcherService_SubscribeBundleResultsClient // Used for receiving *proto.BundleResult (bundle broadcast status info). 26 | 27 | Auth *pkg.AuthenticationService 28 | 29 | ErrChan <-chan error // ErrChan is used for dispatching errors from functions executed within goroutines. 30 | } 31 | 32 | type SimulateBundleConfig struct { 33 | PreExecutionAccountsConfigs []ExecutionAccounts `json:"preExecutionAccountsConfigs"` 34 | PostExecutionAccountsConfigs []ExecutionAccounts `json:"postExecutionAccountsConfigs"` 35 | } 36 | 37 | type ExecutionAccounts struct { 38 | Encoding string `json:"encoding"` 39 | Addresses []string `json:"addresses"` 40 | } 41 | 42 | type SimulateBundleParams struct { 43 | EncodedTransactions []string `json:"encodedTransactions"` 44 | } 45 | 46 | type SimulatedBundleResponse struct { 47 | Context interface{} `json:"context"` 48 | Value SimulatedBundleResponseStruct `json:"value"` 49 | } 50 | 51 | type SimulatedBundleResponseStruct struct { 52 | Summary interface{} `json:"summary"` 53 | TransactionResult []TransactionResult `json:"transactionResults"` 54 | } 55 | 56 | type TransactionResult struct { 57 | Err interface{} `json:"err,omitempty"` 58 | Logs []string `json:"logs,omitempty"` 59 | PreExecutionAccounts []Account `json:"preExecutionAccounts,omitempty"` 60 | PostExecutionAccounts []Account `json:"postExecutionAccounts,omitempty"` 61 | UnitsConsumed *int `json:"unitsConsumed,omitempty"` 62 | ReturnData *ReturnData `json:"returnData,omitempty"` 63 | } 64 | 65 | type Account struct { 66 | Executable bool `json:"executable"` 67 | Owner string `json:"owner"` 68 | Lamports int `json:"lamports"` 69 | Data []string `json:"data"` 70 | RentEpoch *big.Int `json:"rentEpoch,omitempty"` 71 | } 72 | 73 | type ReturnData struct { 74 | ProgramId string `json:"programId"` 75 | Data [2]string `json:"data"` 76 | } 77 | 78 | type BundleStatusesResponse struct { 79 | Jsonrpc string `json:"jsonrpc"` 80 | Result struct { 81 | Context struct { 82 | Slot int `json:"slot"` 83 | } `json:"context"` 84 | Value []struct { 85 | BundleId string `json:"bundle_id"` 86 | Transactions []string `json:"transactions"` 87 | Slot int `json:"slot"` 88 | ConfirmationStatus string `json:"confirmation_status"` 89 | Err struct { 90 | Ok interface{} `json:"Ok"` 91 | } `json:"err"` 92 | } `json:"value"` 93 | } `json:"result"` 94 | Id int `json:"id"` 95 | } 96 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/shredstream_client/shredstream.go: -------------------------------------------------------------------------------- 1 | package shredstream_client 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | 7 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 8 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 9 | "github.com/gagliardetto/solana-go" 10 | "github.com/gagliardetto/solana-go/rpc" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/credentials" 13 | ) 14 | 15 | // disabled until fully working :) 16 | func new(grpcDialURL string, rpcClient *rpc.Client, privateKey solana.PrivateKey, tlsConfig *tls.Config, opts ...grpc.DialOption) (*client, error) { 17 | if tlsConfig != nil { 18 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) 19 | } else { 20 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) 21 | } 22 | 23 | chErr := make(chan error) 24 | conn, err := pkg.CreateAndObserveGRPCConn(context.Background(), chErr, grpcDialURL, opts...) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | shredstreamService := proto.NewShredstreamClient(conn) 30 | authService := pkg.NewAuthenticationService(conn, privateKey) 31 | if err = authService.AuthenticateAndRefresh(proto.Role_SHREDSTREAM_SUBSCRIBER); err != nil { 32 | return nil, err 33 | } 34 | 35 | return &client{ 36 | GrpcConn: conn, 37 | RpcConn: rpcClient, 38 | ShredstreamClient: shredstreamService, 39 | Auth: authService, 40 | }, nil 41 | } 42 | 43 | func (c *client) SendHeartbeat(count uint64, opts ...grpc.CallOption) (*proto.HeartbeatResponse, error) { 44 | return c.ShredstreamClient.SendHeartbeat(c.Auth.GrpcCtx, &proto.Heartbeat{Count: count}, opts...) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/shredstream_client/shredstream_test.go: -------------------------------------------------------------------------------- 1 | package shredstream_client 2 | 3 | /* 4 | func TestMain(m *testing.M) { 5 | _, filename, _, _ := runtime.Caller(0) 6 | godotenv.Load(filepath.Join(filepath.Dir(filename), "..", "..", "..", "jito-go", ".env")) 7 | os.Exit(m.Run()) 8 | } 9 | 10 | 11 | func Test_ShredstreamClient(t *testing.T) { 12 | privKey, ok := os.LookupEnv("PRIVATE_KEY") 13 | if !assert.True(t, ok, "getting PRIVATE_KEY from .env") { 14 | t.FailNow() 15 | } 16 | 17 | client, err := New(jito_go.Amsterdam.ShredReceiverAddr, rpc.New(rpc.MainNetBeta_RPC), solana.MustPrivateKeyFromBase58(privKey), nil) 18 | if assert.NoError(t, err) { 19 | t.FailNow() 20 | } 21 | 22 | for { 23 | 24 | } 25 | } 26 | */ 27 | -------------------------------------------------------------------------------- /pkg/jito-go/clients/shredstream_client/types.go: -------------------------------------------------------------------------------- 1 | package shredstream_client 2 | 3 | import ( 4 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 5 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 6 | "github.com/gagliardetto/solana-go/rpc" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | type client struct { 11 | GrpcConn *grpc.ClientConn 12 | RpcConn *rpc.Client 13 | 14 | ShredstreamClient proto.ShredstreamClient 15 | 16 | Auth *pkg.AuthenticationService 17 | } 18 | -------------------------------------------------------------------------------- /pkg/jito-go/constants.go: -------------------------------------------------------------------------------- 1 | package jito_go 2 | 3 | import ( 4 | "github.com/gagliardetto/solana-go" 5 | ) 6 | 7 | var MainnetTipPaymentProgram = solana.MustPublicKeyFromBase58("T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt") 8 | var MainnetTipDistributionProgram = solana.MustPublicKeyFromBase58("4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7") 9 | var MainnetMerkleUploadAuthorityProgram = solana.MustPublicKeyFromBase58("GZctHpWXmsZC1YHACTGGcHhYxjdRqQvTpYkb9LMvxDib") 10 | 11 | var MainnetTipAccounts = []solana.PublicKey{ 12 | solana.MustPublicKeyFromBase58("96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5"), 13 | solana.MustPublicKeyFromBase58("HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe"), 14 | solana.MustPublicKeyFromBase58("Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY"), 15 | solana.MustPublicKeyFromBase58("ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49"), 16 | solana.MustPublicKeyFromBase58("DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh"), 17 | solana.MustPublicKeyFromBase58("ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt"), 18 | solana.MustPublicKeyFromBase58("DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL"), 19 | solana.MustPublicKeyFromBase58("3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT"), 20 | } 21 | 22 | var TestnetTipPaymentProgram = solana.MustPublicKeyFromBase58("DCN82qDxJAQuSqHhv2BJuAgi41SPeKZB5ioBCTMNDrCC") 23 | var TestnetTipDistributionProgram = solana.MustPublicKeyFromBase58("F2Zu7QZiTYUhPd7u9ukRVwxh7B71oA3NMJcHuCHc29P2") 24 | var TestnetMerkleUploadAuthorityProgram = solana.MustPublicKeyFromBase58("GZctHpWXmsZC1YHACTGGcHhYxjdRqQvTpYkb9LMvxDib") 25 | 26 | var TestnetTipAccounts = []solana.PublicKey{ 27 | solana.MustPublicKeyFromBase58("B1mrQSpdeMU9gCvkJ6VsXVVoYjRGkNA7TtjMyqxrhecH"), 28 | solana.MustPublicKeyFromBase58("aTtUk2DHgLhKZRDjePq6eiHRKC1XXFMBiSUfQ2JNDbN"), 29 | solana.MustPublicKeyFromBase58("E2eSqe33tuhAHKTrwky5uEjaVqnb2T9ns6nHHUrN8588"), 30 | solana.MustPublicKeyFromBase58("4xgEmT58RwTNsF5xm2RMYCnR1EVukdK8a1i2qFjnJFu3"), 31 | solana.MustPublicKeyFromBase58("EoW3SUQap7ZeynXQ2QJ847aerhxbPVr843uMeTfc9dxM"), 32 | solana.MustPublicKeyFromBase58("ARTtviJkLLt6cHGQDydfo1Wyk6M4VGZdKZ2ZhdnJL336"), 33 | solana.MustPublicKeyFromBase58("9n3d1K5YD2vECAbRFhFFGYNNjiXtHXJWn9F31t89vsAV"), 34 | solana.MustPublicKeyFromBase58("9ttgPBBhRYFuQccdR1DSnb7hydsWANoDsV3P9kaGMCEh"), 35 | } 36 | 37 | type JitoEndpointInfo struct { 38 | Region string 39 | BlockEngineURL string 40 | RelayerURL string 41 | ShredReceiverAddr string 42 | Ntp string 43 | } 44 | 45 | var JitoEndpoints = map[string]JitoEndpointInfo{ 46 | "AMS": { 47 | Region: "amsterdam", 48 | BlockEngineURL: "amsterdam.mainnet.block-engine.jito.wtf:443", 49 | RelayerURL: "amsterdam.mainnet.relayer.jito.wtf:8100", 50 | ShredReceiverAddr: "74.118.140.240:1002", 51 | Ntp: "ntp.amsterdam.jito.wtf", 52 | }, 53 | "FFM": { 54 | Region: "frankfurt", 55 | BlockEngineURL: "frankfurt.mainnet.block-engine.jito.wtf:443", 56 | RelayerURL: "frankfurt.mainnet.relayer.jito.wtf:8100", 57 | ShredReceiverAddr: "145.40.93.84:1002", 58 | Ntp: "ntp.frankfurt.jito.wtf", 59 | }, 60 | "NY": { 61 | Region: "ny", 62 | BlockEngineURL: "ny.mainnet.block-engine.jito.wtf:443", 63 | RelayerURL: "ny.mainnet.relayer.jito.wtf:8100", 64 | ShredReceiverAddr: "141.98.216.96:1002", 65 | Ntp: "ntp.dallas.jito.wtf", 66 | }, 67 | "TKY": { 68 | Region: "tokyo", 69 | BlockEngineURL: "tokyo.mainnet.block-engine.jito.wtf:443", 70 | RelayerURL: "tokyo.mainnet.relayer.jito.wtf:8100", 71 | ShredReceiverAddr: "202.8.9.160:1002", 72 | Ntp: "ntp.tokyo.jito.wtf", 73 | }, 74 | "BigD-TESTNET": { 75 | BlockEngineURL: "dallas.testnet.block-engine.jito.wtf:443", 76 | RelayerURL: "dallas.testnet.relayer.jito.wtf:8100", 77 | ShredReceiverAddr: "147.28.154.132:1002", 78 | Ntp: "ntp.dallas.jito.wtf", 79 | }, 80 | "NY-TESTNET": { 81 | BlockEngineURL: "ny.testnet.block-engine.jito.wtf:443", 82 | RelayerURL: "nyc.testnet.relayer.jito.wtf:8100", 83 | ShredReceiverAddr: "141.98.216.97:1002", 84 | Ntp: "ntp.dallas.jito.wtf", // Dallas NTP is suitable for NY connections (from jito's doc) 85 | }, 86 | } 87 | 88 | var Amsterdam = JitoEndpoints["AMS"] 89 | var Frankfurt = JitoEndpoints["FFM"] 90 | var NewYork = JitoEndpoints["NY"] 91 | var Tokyo = JitoEndpoints["TKY"] 92 | var TestnetDallas = JitoEndpoints["BigD-TESTNET"] 93 | var TestnetNewYork = JitoEndpoints["NY-TESTNET"] 94 | 95 | const JitoMainnet = "mainnet.rpc.jito.wtf" 96 | -------------------------------------------------------------------------------- /pkg/jito-go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/1fge/pump-fun-sniper-bot/pkg/jito-go 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/gagliardetto/binary v0.8.0 7 | github.com/gagliardetto/solana-go v1.10.0 8 | github.com/gorilla/websocket v1.5.1 9 | github.com/joho/godotenv v1.5.1 10 | github.com/mr-tron/base58 v1.2.0 11 | github.com/stretchr/testify v1.9.0 12 | google.golang.org/grpc v1.63.2 13 | google.golang.org/protobuf v1.34.1 14 | ) 15 | 16 | require ( 17 | contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect 18 | filippo.io/edwards25519 v1.1.0 // indirect 19 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect 20 | github.com/avast/retry-go v3.0.0+incompatible // indirect 21 | github.com/benbjohnson/clock v1.3.5 // indirect 22 | github.com/beorn7/perks v1.0.1 // indirect 23 | github.com/blendle/zapdriver v1.3.1 // indirect 24 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 25 | github.com/davecgh/go-spew v1.1.1 // indirect 26 | github.com/dfuse-io/logging v0.0.0-20201110202154-26697de88c79 // indirect 27 | github.com/fatih/color v1.16.0 // indirect 28 | github.com/gagliardetto/treeout v0.1.4 // indirect 29 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 30 | github.com/golang/protobuf v1.5.4 // indirect 31 | github.com/google/uuid v1.6.0 // indirect 32 | github.com/json-iterator/go v1.1.12 // indirect 33 | github.com/klauspost/compress v1.17.8 // indirect 34 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 35 | github.com/magiconair/properties v1.8.7 // indirect 36 | github.com/mattn/go-colorable v0.1.13 // indirect 37 | github.com/mattn/go-isatty v0.0.20 // indirect 38 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 39 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect 43 | github.com/pmezard/go-difflib v1.0.0 // indirect 44 | github.com/prometheus/client_golang v1.19.0 // indirect 45 | github.com/prometheus/client_model v0.6.0 // indirect 46 | github.com/prometheus/common v0.50.0 // indirect 47 | github.com/prometheus/procfs v0.13.0 // indirect 48 | github.com/rs/zerolog v1.32.0 // indirect 49 | github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 // indirect 50 | github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect 51 | github.com/tidwall/gjson v1.17.1 // indirect 52 | github.com/tidwall/match v1.1.1 // indirect 53 | github.com/tidwall/pretty v1.2.1 // indirect 54 | go.mongodb.org/mongo-driver v1.15.0 // indirect 55 | go.opencensus.io v0.24.0 // indirect 56 | go.uber.org/atomic v1.11.0 // indirect 57 | go.uber.org/multierr v1.11.0 // indirect 58 | go.uber.org/ratelimit v0.3.1 // indirect 59 | go.uber.org/zap v1.27.0 // indirect 60 | golang.org/x/crypto v0.23.0 // indirect 61 | golang.org/x/net v0.25.0 // indirect 62 | golang.org/x/sys v0.20.0 // indirect 63 | golang.org/x/term v0.20.0 // indirect 64 | golang.org/x/text v0.15.0 // indirect 65 | golang.org/x/time v0.5.0 // indirect 66 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | ) 69 | -------------------------------------------------------------------------------- /pkg/jito-go/pkg/convert.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 5 | bin "github.com/gagliardetto/binary" 6 | "github.com/gagliardetto/solana-go" 7 | ) 8 | 9 | // ConvertTransactionToProtobufPacket converts a solana-go Transaction to a proto.Packet. 10 | func ConvertTransactionToProtobufPacket(transaction *solana.Transaction) (proto.Packet, error) { 11 | data, err := transaction.MarshalBinary() 12 | if err != nil { 13 | return proto.Packet{}, err 14 | } 15 | 16 | return proto.Packet{ 17 | Data: data, 18 | Meta: &proto.Meta{ 19 | Size: uint64(len(data)), 20 | Addr: "", 21 | Port: 0, 22 | Flags: nil, 23 | SenderStake: 0, 24 | }, 25 | }, nil 26 | } 27 | 28 | // ConvertBatchTransactionToProtobufPacket converts a slice of solana-go Transaction to a slice of proto.Packet. 29 | func ConvertBatchTransactionToProtobufPacket(transactions []*solana.Transaction) ([]*proto.Packet, error) { 30 | packets := make([]*proto.Packet, 0, len(transactions)) 31 | for _, tx := range transactions { 32 | packet, err := ConvertTransactionToProtobufPacket(tx) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | packets = append(packets, &packet) 38 | } 39 | 40 | return packets, nil 41 | } 42 | 43 | // ConvertProtobufPacketToTransaction converts a proto.Packet to a solana-go Transaction. 44 | func ConvertProtobufPacketToTransaction(packet *proto.Packet) (*solana.Transaction, error) { 45 | tx := &solana.Transaction{} 46 | err := tx.UnmarshalWithDecoder(bin.NewBorshDecoder(packet.Data)) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return tx, nil 52 | } 53 | 54 | // ConvertBatchProtobufPacketToTransaction converts a slice of proto.Packet to a slice of solana-go Transaction. 55 | func ConvertBatchProtobufPacketToTransaction(packets []*proto.Packet) ([]*solana.Transaction, error) { 56 | txs := make([]*solana.Transaction, 0, len(packets)) 57 | for _, packet := range packets { 58 | tx, err := ConvertProtobufPacketToTransaction(packet) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | txs = append(txs, tx) 64 | } 65 | 66 | return txs, nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/jito-go/pkg/grpc.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "context" 5 | "google.golang.org/grpc" 6 | "google.golang.org/grpc/connectivity" 7 | "time" 8 | ) 9 | 10 | // CreateAndObserveGRPCConn creates a new gRPC connection and observes its conn status. 11 | func CreateAndObserveGRPCConn(ctx context.Context, chErr chan error, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 12 | conn, err := grpc.NewClient(target, opts...) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | go func() { 18 | var retries int 19 | for { 20 | select { 21 | case <-ctx.Done(): 22 | if err = conn.Close(); err != nil { 23 | chErr <- err 24 | } 25 | return 26 | default: 27 | state := conn.GetState() 28 | if state == connectivity.Ready { 29 | retries = 0 30 | time.Sleep(1 * time.Second) 31 | continue 32 | } 33 | 34 | if state == connectivity.TransientFailure || state == connectivity.Connecting || state == connectivity.Idle { 35 | if retries < 5 { 36 | time.Sleep(time.Duration(retries) * time.Second) 37 | conn.ResetConnectBackoff() 38 | retries++ 39 | } else { 40 | conn.Close() 41 | conn, err = grpc.NewClient(target, opts...) 42 | if err != nil { 43 | chErr <- err 44 | } 45 | retries = 0 46 | } 47 | } else if state == connectivity.Shutdown { 48 | conn, err = grpc.NewClient(target, opts...) 49 | if err != nil { 50 | chErr <- err 51 | } 52 | retries = 0 53 | } 54 | 55 | if !conn.WaitForStateChange(ctx, state) { 56 | continue 57 | } 58 | } 59 | } 60 | }() 61 | 62 | return conn, nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/jito-go/pkg/token_authenticator.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | 9 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/proto" 10 | "github.com/gagliardetto/solana-go" 11 | "github.com/mr-tron/base58" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/metadata" 14 | ) 15 | 16 | type AuthenticationService struct { 17 | AuthService proto.AuthServiceClient 18 | GrpcCtx context.Context 19 | KeyPair *Keypair 20 | BearerToken string 21 | ExpiresAt int64 // seconds 22 | ErrChan chan error 23 | mu sync.Mutex 24 | } 25 | 26 | func NewAuthenticationService(grpcConn *grpc.ClientConn, privateKey solana.PrivateKey) *AuthenticationService { 27 | return &AuthenticationService{ 28 | GrpcCtx: context.Background(), 29 | AuthService: proto.NewAuthServiceClient(grpcConn), 30 | KeyPair: NewKeyPair(privateKey), 31 | ErrChan: make(chan error, 1), 32 | mu: sync.Mutex{}, 33 | } 34 | } 35 | 36 | // AuthenticateAndRefresh is a function that authenticates the client and refreshes the access token. 37 | func (as *AuthenticationService) AuthenticateAndRefresh(role proto.Role) error { 38 | respChallenge, err := as.AuthService.GenerateAuthChallenge(as.GrpcCtx, 39 | &proto.GenerateAuthChallengeRequest{ 40 | Role: role, 41 | Pubkey: as.KeyPair.PublicKey.Bytes(), 42 | }, 43 | ) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | challenge := fmt.Sprintf("%s-%s", as.KeyPair.PublicKey.String(), respChallenge.GetChallenge()) 49 | 50 | sig, err := as.generateChallengeSignature([]byte(challenge)) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | respToken, err := as.AuthService.GenerateAuthTokens(as.GrpcCtx, &proto.GenerateAuthTokensRequest{ 56 | Challenge: challenge, 57 | SignedChallenge: sig, 58 | ClientPubkey: as.KeyPair.PublicKey.Bytes(), 59 | }) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | as.updateAuthorizationMetadata(respToken.AccessToken) 65 | 66 | go func() { 67 | for { 68 | select { 69 | case <-as.GrpcCtx.Done(): 70 | as.ErrChan <- as.GrpcCtx.Err() 71 | default: 72 | var resp *proto.RefreshAccessTokenResponse 73 | resp, err = as.AuthService.RefreshAccessToken(as.GrpcCtx, &proto.RefreshAccessTokenRequest{ 74 | RefreshToken: respToken.RefreshToken.Value, 75 | }) 76 | if err != nil { 77 | as.ErrChan <- fmt.Errorf("failed to refresh access token: %w", err) 78 | continue 79 | } 80 | 81 | as.updateAuthorizationMetadata(resp.AccessToken) 82 | time.Sleep(time.Until(resp.AccessToken.ExpiresAtUtc.AsTime()) - 15*time.Second) 83 | } 84 | } 85 | }() 86 | 87 | return nil 88 | } 89 | 90 | // updateAuthorizationMetadata updates headers of the gRPC connection. 91 | func (as *AuthenticationService) updateAuthorizationMetadata(token *proto.Token) { 92 | as.mu.Lock() 93 | defer as.mu.Unlock() 94 | 95 | as.GrpcCtx = metadata.NewOutgoingContext(context.Background(), metadata.Pairs("authorization", "Bearer "+token.Value)) 96 | as.BearerToken = token.Value 97 | as.ExpiresAt = token.ExpiresAtUtc.Seconds 98 | } 99 | 100 | func (as *AuthenticationService) generateChallengeSignature(challenge []byte) ([]byte, error) { 101 | sig, err := as.KeyPair.PrivateKey.Sign(challenge) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | return base58.Decode(sig.String()) 107 | } 108 | -------------------------------------------------------------------------------- /pkg/jito-go/pkg/types.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/gagliardetto/solana-go" 5 | "time" 6 | ) 7 | 8 | type Keypair struct { 9 | PublicKey solana.PublicKey 10 | PrivateKey solana.PrivateKey 11 | } 12 | 13 | type Platform string 14 | 15 | var ( 16 | SolanaFM Platform = "https://solana.fm/tx/" 17 | Solscan Platform = "https://solscan.io/tx/" 18 | SolanaExplorer Platform = "https://explorer.solana.com/tx/" 19 | SolanaBeach Platform = "https://solanabeach.io/transaction/" 20 | XRAY Platform = "https://xray.helius.xyz/tx/" 21 | ) 22 | 23 | const tipStreamURL = "ws://bundles-api-rest.jito.wtf/api/v1/bundles/tip_stream" 24 | 25 | type TipStreamInfo struct { 26 | Time time.Time `json:"time"` 27 | LandedTips25ThPercentile float64 `json:"landed_tips_25th_percentile"` 28 | LandedTips50ThPercentile float64 `json:"landed_tips_50th_percentile"` 29 | LandedTips75ThPercentile float64 `json:"landed_tips_75th_percentile"` 30 | LandedTips95ThPercentile float64 `json:"landed_tips_95th_percentile"` 31 | LandedTips99ThPercentile float64 `json:"landed_tips_99th_percentile"` 32 | EmaLandedTips50ThPercentile float64 `json:"ema_landed_tips_50th_percentile"` 33 | } 34 | -------------------------------------------------------------------------------- /pkg/jito-go/pkg/util.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | "time" 8 | 9 | "github.com/gagliardetto/solana-go" 10 | "github.com/gorilla/websocket" 11 | ) 12 | 13 | // ExtractSigFromTx extracts the transaction's signature. 14 | func ExtractSigFromTx(tx *solana.Transaction) solana.Signature { 15 | return tx.Signatures[0] 16 | } 17 | 18 | func BatchExtractSigFromTx(txns []*solana.Transaction) []solana.Signature { 19 | sigs := make([]solana.Signature, 0, len(txns)) 20 | for _, tx := range txns { 21 | sigs = append(sigs, tx.Signatures[0]) 22 | } 23 | return sigs 24 | } 25 | 26 | func BuildTransactionLinks(txns []solana.Signature, platform Platform) []string { 27 | txs := make([]string, 0, len(txns)) 28 | for _, tx := range txns { 29 | txs = append(txs, fmt.Sprintf("%s%s", platform, tx.String())) 30 | } 31 | return txs 32 | } 33 | 34 | // NewKeyPair creates a Keypair from a private key. 35 | func NewKeyPair(privateKey solana.PrivateKey) *Keypair { 36 | return &Keypair{PrivateKey: privateKey, PublicKey: privateKey.PublicKey()} 37 | } 38 | 39 | // SubscribeTipStream establishes a connection to the Jito websocket and receives TipStreamInfo. 40 | func SubscribeTipStream(ctx context.Context) (<-chan *TipStreamInfo, <-chan error, error) { 41 | dialer := websocket.Dialer{} 42 | ch := make(chan *TipStreamInfo) 43 | chErr := make(chan error) 44 | 45 | conn, _, err := dialer.Dial(tipStreamURL, nil) 46 | if err != nil { 47 | return nil, nil, err 48 | } 49 | 50 | go func() { 51 | defer conn.Close() 52 | for { 53 | select { 54 | case <-ctx.Done(): 55 | return 56 | default: 57 | var r []TipStreamInfo 58 | if err = conn.ReadJSON(&r); err != nil { 59 | fmt.Println("err hson", err) 60 | chErr <- err 61 | time.Sleep(10 * time.Millisecond) 62 | continue 63 | } 64 | 65 | ch <- &r[0] 66 | } 67 | } 68 | }() 69 | 70 | return ch, chErr, nil 71 | } 72 | 73 | // GenerateKeypair creates a new Solana Keypair. 74 | func GenerateKeypair() *Keypair { 75 | wallet := solana.NewWallet() 76 | return &Keypair{PublicKey: wallet.PublicKey(), PrivateKey: wallet.PrivateKey} 77 | } 78 | 79 | func LamportsToSol(lamports *big.Float) *big.Float { 80 | return new(big.Float).Quo(lamports, new(big.Float).SetUint64(solana.LAMPORTS_PER_SOL)) 81 | } 82 | 83 | // StrSliceToByteSlice converts a string array to a byte array. 84 | func StrSliceToByteSlice(s []string) [][]byte { 85 | byteSlice := make([][]byte, 0, len(s)) 86 | for _, b := range s { 87 | byteSlice = append(byteSlice, []byte(b)) 88 | } 89 | return byteSlice 90 | } 91 | -------------------------------------------------------------------------------- /pkg/jito-go/proto/auth_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc v4.25.2 5 | // source: auth.proto 6 | 7 | package proto 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | AuthService_GenerateAuthChallenge_FullMethodName = "/auth.AuthService/GenerateAuthChallenge" 23 | AuthService_GenerateAuthTokens_FullMethodName = "/auth.AuthService/GenerateAuthTokens" 24 | AuthService_RefreshAccessToken_FullMethodName = "/auth.AuthService/RefreshAccessToken" 25 | ) 26 | 27 | // AuthServiceClient is the client API for AuthService service. 28 | // 29 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 30 | type AuthServiceClient interface { 31 | // / Returns a challenge, client is expected to sign this challenge with an appropriate keypair in order to obtain access tokens. 32 | GenerateAuthChallenge(ctx context.Context, in *GenerateAuthChallengeRequest, opts ...grpc.CallOption) (*GenerateAuthChallengeResponse, error) 33 | // / Provides the client with the initial pair of auth tokens for API access. 34 | GenerateAuthTokens(ctx context.Context, in *GenerateAuthTokensRequest, opts ...grpc.CallOption) (*GenerateAuthTokensResponse, error) 35 | // / Call this method with a non-expired refresh token to obtain a new access token. 36 | RefreshAccessToken(ctx context.Context, in *RefreshAccessTokenRequest, opts ...grpc.CallOption) (*RefreshAccessTokenResponse, error) 37 | } 38 | 39 | type authServiceClient struct { 40 | cc grpc.ClientConnInterface 41 | } 42 | 43 | func NewAuthServiceClient(cc grpc.ClientConnInterface) AuthServiceClient { 44 | return &authServiceClient{cc} 45 | } 46 | 47 | func (c *authServiceClient) GenerateAuthChallenge(ctx context.Context, in *GenerateAuthChallengeRequest, opts ...grpc.CallOption) (*GenerateAuthChallengeResponse, error) { 48 | out := new(GenerateAuthChallengeResponse) 49 | err := c.cc.Invoke(ctx, AuthService_GenerateAuthChallenge_FullMethodName, in, out, opts...) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return out, nil 54 | } 55 | 56 | func (c *authServiceClient) GenerateAuthTokens(ctx context.Context, in *GenerateAuthTokensRequest, opts ...grpc.CallOption) (*GenerateAuthTokensResponse, error) { 57 | out := new(GenerateAuthTokensResponse) 58 | err := c.cc.Invoke(ctx, AuthService_GenerateAuthTokens_FullMethodName, in, out, opts...) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return out, nil 63 | } 64 | 65 | func (c *authServiceClient) RefreshAccessToken(ctx context.Context, in *RefreshAccessTokenRequest, opts ...grpc.CallOption) (*RefreshAccessTokenResponse, error) { 66 | out := new(RefreshAccessTokenResponse) 67 | err := c.cc.Invoke(ctx, AuthService_RefreshAccessToken_FullMethodName, in, out, opts...) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return out, nil 72 | } 73 | 74 | // AuthServiceServer is the server API for AuthService service. 75 | // All implementations must embed UnimplementedAuthServiceServer 76 | // for forward compatibility 77 | type AuthServiceServer interface { 78 | // / Returns a challenge, client is expected to sign this challenge with an appropriate keypair in order to obtain access tokens. 79 | GenerateAuthChallenge(context.Context, *GenerateAuthChallengeRequest) (*GenerateAuthChallengeResponse, error) 80 | // / Provides the client with the initial pair of auth tokens for API access. 81 | GenerateAuthTokens(context.Context, *GenerateAuthTokensRequest) (*GenerateAuthTokensResponse, error) 82 | // / Call this method with a non-expired refresh token to obtain a new access token. 83 | RefreshAccessToken(context.Context, *RefreshAccessTokenRequest) (*RefreshAccessTokenResponse, error) 84 | mustEmbedUnimplementedAuthServiceServer() 85 | } 86 | 87 | // UnimplementedAuthServiceServer must be embedded to have forward compatible implementations. 88 | type UnimplementedAuthServiceServer struct { 89 | } 90 | 91 | func (UnimplementedAuthServiceServer) GenerateAuthChallenge(context.Context, *GenerateAuthChallengeRequest) (*GenerateAuthChallengeResponse, error) { 92 | return nil, status.Errorf(codes.Unimplemented, "method GenerateAuthChallenge not implemented") 93 | } 94 | func (UnimplementedAuthServiceServer) GenerateAuthTokens(context.Context, *GenerateAuthTokensRequest) (*GenerateAuthTokensResponse, error) { 95 | return nil, status.Errorf(codes.Unimplemented, "method GenerateAuthTokens not implemented") 96 | } 97 | func (UnimplementedAuthServiceServer) RefreshAccessToken(context.Context, *RefreshAccessTokenRequest) (*RefreshAccessTokenResponse, error) { 98 | return nil, status.Errorf(codes.Unimplemented, "method RefreshAccessToken not implemented") 99 | } 100 | func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} 101 | 102 | // UnsafeAuthServiceServer may be embedded to opt out of forward compatibility for this service. 103 | // Use of this interface is not recommended, as added methods to AuthServiceServer will 104 | // result in compilation errors. 105 | type UnsafeAuthServiceServer interface { 106 | mustEmbedUnimplementedAuthServiceServer() 107 | } 108 | 109 | func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) { 110 | s.RegisterService(&AuthService_ServiceDesc, srv) 111 | } 112 | 113 | func _AuthService_GenerateAuthChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 114 | in := new(GenerateAuthChallengeRequest) 115 | if err := dec(in); err != nil { 116 | return nil, err 117 | } 118 | if interceptor == nil { 119 | return srv.(AuthServiceServer).GenerateAuthChallenge(ctx, in) 120 | } 121 | info := &grpc.UnaryServerInfo{ 122 | Server: srv, 123 | FullMethod: AuthService_GenerateAuthChallenge_FullMethodName, 124 | } 125 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 126 | return srv.(AuthServiceServer).GenerateAuthChallenge(ctx, req.(*GenerateAuthChallengeRequest)) 127 | } 128 | return interceptor(ctx, in, info, handler) 129 | } 130 | 131 | func _AuthService_GenerateAuthTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 132 | in := new(GenerateAuthTokensRequest) 133 | if err := dec(in); err != nil { 134 | return nil, err 135 | } 136 | if interceptor == nil { 137 | return srv.(AuthServiceServer).GenerateAuthTokens(ctx, in) 138 | } 139 | info := &grpc.UnaryServerInfo{ 140 | Server: srv, 141 | FullMethod: AuthService_GenerateAuthTokens_FullMethodName, 142 | } 143 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 144 | return srv.(AuthServiceServer).GenerateAuthTokens(ctx, req.(*GenerateAuthTokensRequest)) 145 | } 146 | return interceptor(ctx, in, info, handler) 147 | } 148 | 149 | func _AuthService_RefreshAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 150 | in := new(RefreshAccessTokenRequest) 151 | if err := dec(in); err != nil { 152 | return nil, err 153 | } 154 | if interceptor == nil { 155 | return srv.(AuthServiceServer).RefreshAccessToken(ctx, in) 156 | } 157 | info := &grpc.UnaryServerInfo{ 158 | Server: srv, 159 | FullMethod: AuthService_RefreshAccessToken_FullMethodName, 160 | } 161 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 162 | return srv.(AuthServiceServer).RefreshAccessToken(ctx, req.(*RefreshAccessTokenRequest)) 163 | } 164 | return interceptor(ctx, in, info, handler) 165 | } 166 | 167 | // AuthService_ServiceDesc is the grpc.ServiceDesc for AuthService service. 168 | // It's only intended for direct use with grpc.RegisterService, 169 | // and not to be introspected or modified (even as a copy) 170 | var AuthService_ServiceDesc = grpc.ServiceDesc{ 171 | ServiceName: "auth.AuthService", 172 | HandlerType: (*AuthServiceServer)(nil), 173 | Methods: []grpc.MethodDesc{ 174 | { 175 | MethodName: "GenerateAuthChallenge", 176 | Handler: _AuthService_GenerateAuthChallenge_Handler, 177 | }, 178 | { 179 | MethodName: "GenerateAuthTokens", 180 | Handler: _AuthService_GenerateAuthTokens_Handler, 181 | }, 182 | { 183 | MethodName: "RefreshAccessToken", 184 | Handler: _AuthService_RefreshAccessToken_Handler, 185 | }, 186 | }, 187 | Streams: []grpc.StreamDesc{}, 188 | Metadata: "auth.proto", 189 | } 190 | -------------------------------------------------------------------------------- /pkg/jito-go/proto/block.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.32.0 4 | // protoc v4.25.2 5 | // source: block.proto 6 | 7 | package proto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | // Condensed block helpful for getting data around efficiently internal to our system. 24 | type CondensedBlock struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | Header *Header `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` 30 | PreviousBlockhash string `protobuf:"bytes,2,opt,name=previous_blockhash,json=previousBlockhash,proto3" json:"previous_blockhash,omitempty"` 31 | Blockhash string `protobuf:"bytes,3,opt,name=blockhash,proto3" json:"blockhash,omitempty"` 32 | ParentSlot uint64 `protobuf:"varint,4,opt,name=parent_slot,json=parentSlot,proto3" json:"parent_slot,omitempty"` 33 | VersionedTransactions [][]byte `protobuf:"bytes,5,rep,name=versioned_transactions,json=versionedTransactions,proto3" json:"versioned_transactions,omitempty"` 34 | Slot uint64 `protobuf:"varint,6,opt,name=slot,proto3" json:"slot,omitempty"` 35 | Commitment string `protobuf:"bytes,7,opt,name=commitment,proto3" json:"commitment,omitempty"` 36 | } 37 | 38 | func (x *CondensedBlock) Reset() { 39 | *x = CondensedBlock{} 40 | if protoimpl.UnsafeEnabled { 41 | mi := &file_block_proto_msgTypes[0] 42 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 43 | ms.StoreMessageInfo(mi) 44 | } 45 | } 46 | 47 | func (x *CondensedBlock) String() string { 48 | return protoimpl.X.MessageStringOf(x) 49 | } 50 | 51 | func (*CondensedBlock) ProtoMessage() {} 52 | 53 | func (x *CondensedBlock) ProtoReflect() protoreflect.Message { 54 | mi := &file_block_proto_msgTypes[0] 55 | if protoimpl.UnsafeEnabled && x != nil { 56 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 57 | if ms.LoadMessageInfo() == nil { 58 | ms.StoreMessageInfo(mi) 59 | } 60 | return ms 61 | } 62 | return mi.MessageOf(x) 63 | } 64 | 65 | // Deprecated: Use CondensedBlock.ProtoReflect.Descriptor instead. 66 | func (*CondensedBlock) Descriptor() ([]byte, []int) { 67 | return file_block_proto_rawDescGZIP(), []int{0} 68 | } 69 | 70 | func (x *CondensedBlock) GetHeader() *Header { 71 | if x != nil { 72 | return x.Header 73 | } 74 | return nil 75 | } 76 | 77 | func (x *CondensedBlock) GetPreviousBlockhash() string { 78 | if x != nil { 79 | return x.PreviousBlockhash 80 | } 81 | return "" 82 | } 83 | 84 | func (x *CondensedBlock) GetBlockhash() string { 85 | if x != nil { 86 | return x.Blockhash 87 | } 88 | return "" 89 | } 90 | 91 | func (x *CondensedBlock) GetParentSlot() uint64 { 92 | if x != nil { 93 | return x.ParentSlot 94 | } 95 | return 0 96 | } 97 | 98 | func (x *CondensedBlock) GetVersionedTransactions() [][]byte { 99 | if x != nil { 100 | return x.VersionedTransactions 101 | } 102 | return nil 103 | } 104 | 105 | func (x *CondensedBlock) GetSlot() uint64 { 106 | if x != nil { 107 | return x.Slot 108 | } 109 | return 0 110 | } 111 | 112 | func (x *CondensedBlock) GetCommitment() string { 113 | if x != nil { 114 | return x.Commitment 115 | } 116 | return "" 117 | } 118 | 119 | var File_block_proto protoreflect.FileDescriptor 120 | 121 | var file_block_proto_rawDesc = []byte{ 122 | 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x62, 123 | 0x6c, 0x6f, 0x63, 0x6b, 0x1a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 124 | 0x74, 0x6f, 0x22, 0x91, 0x02, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x64, 125 | 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x26, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 126 | 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x48, 127 | 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2d, 0x0a, 128 | 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x68, 129 | 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x72, 0x65, 0x76, 0x69, 130 | 0x6f, 0x75, 0x73, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 131 | 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 132 | 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 133 | 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 134 | 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x6c, 0x6f, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x76, 135 | 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 136 | 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x15, 0x76, 0x65, 0x72, 137 | 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 138 | 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 139 | 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 140 | 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 141 | 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x04, 0x5a, 0x02, 0x2e, 0x2f, 0x62, 0x06, 0x70, 0x72, 142 | 0x6f, 0x74, 0x6f, 0x33, 143 | } 144 | 145 | var ( 146 | file_block_proto_rawDescOnce sync.Once 147 | file_block_proto_rawDescData = file_block_proto_rawDesc 148 | ) 149 | 150 | func file_block_proto_rawDescGZIP() []byte { 151 | file_block_proto_rawDescOnce.Do(func() { 152 | file_block_proto_rawDescData = protoimpl.X.CompressGZIP(file_block_proto_rawDescData) 153 | }) 154 | return file_block_proto_rawDescData 155 | } 156 | 157 | var file_block_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 158 | var file_block_proto_goTypes = []interface{}{ 159 | (*CondensedBlock)(nil), // 0: block.CondensedBlock 160 | (*Header)(nil), // 1: shared.Header 161 | } 162 | var file_block_proto_depIdxs = []int32{ 163 | 1, // 0: block.CondensedBlock.header:type_name -> shared.Header 164 | 1, // [1:1] is the sub-list for method output_type 165 | 1, // [1:1] is the sub-list for method input_type 166 | 1, // [1:1] is the sub-list for extension type_name 167 | 1, // [1:1] is the sub-list for extension extendee 168 | 0, // [0:1] is the sub-list for field type_name 169 | } 170 | 171 | func init() { file_block_proto_init() } 172 | func file_block_proto_init() { 173 | if File_block_proto != nil { 174 | return 175 | } 176 | file_shared_proto_init() 177 | if !protoimpl.UnsafeEnabled { 178 | file_block_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 179 | switch v := v.(*CondensedBlock); i { 180 | case 0: 181 | return &v.state 182 | case 1: 183 | return &v.sizeCache 184 | case 2: 185 | return &v.unknownFields 186 | default: 187 | return nil 188 | } 189 | } 190 | } 191 | type x struct{} 192 | out := protoimpl.TypeBuilder{ 193 | File: protoimpl.DescBuilder{ 194 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 195 | RawDescriptor: file_block_proto_rawDesc, 196 | NumEnums: 0, 197 | NumMessages: 1, 198 | NumExtensions: 0, 199 | NumServices: 0, 200 | }, 201 | GoTypes: file_block_proto_goTypes, 202 | DependencyIndexes: file_block_proto_depIdxs, 203 | MessageInfos: file_block_proto_msgTypes, 204 | }.Build() 205 | File_block_proto = out.File 206 | file_block_proto_rawDesc = nil 207 | file_block_proto_goTypes = nil 208 | file_block_proto_depIdxs = nil 209 | } 210 | -------------------------------------------------------------------------------- /pkg/jito-go/proto/relayer_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc v4.25.2 5 | // source: relayer.proto 6 | 7 | package proto 8 | 9 | import ( 10 | "context" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Relayer_GetTpuConfigs_FullMethodName = "/relayer.Relayer/GetTpuConfigs" 23 | Relayer_SubscribePackets_FullMethodName = "/relayer.Relayer/SubscribePackets" 24 | ) 25 | 26 | // RelayerClient is the client API for Relayer service. 27 | // 28 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 29 | type RelayerClient interface { 30 | // The relayer has TPU and TPU forward sockets that validator-watcher can leverage. 31 | // A validator can fetch this config and change its TPU and TPU forward port in gossip. 32 | GetTpuConfigs(ctx context.Context, in *GetTpuConfigsRequest, opts ...grpc.CallOption) (*GetTpuConfigsResponse, error) 33 | // Validators can subscribe to packets from the relayer and receive a multiplexed signal that contains a mixture 34 | // of packets and heartbeats 35 | SubscribePackets(ctx context.Context, in *SubscribePacketsRequest, opts ...grpc.CallOption) (Relayer_SubscribePacketsClient, error) 36 | } 37 | 38 | type relayerClient struct { 39 | cc grpc.ClientConnInterface 40 | } 41 | 42 | func NewRelayerClient(cc grpc.ClientConnInterface) RelayerClient { 43 | return &relayerClient{cc} 44 | } 45 | 46 | func (c *relayerClient) GetTpuConfigs(ctx context.Context, in *GetTpuConfigsRequest, opts ...grpc.CallOption) (*GetTpuConfigsResponse, error) { 47 | out := new(GetTpuConfigsResponse) 48 | err := c.cc.Invoke(ctx, Relayer_GetTpuConfigs_FullMethodName, in, out, opts...) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return out, nil 53 | } 54 | 55 | func (c *relayerClient) SubscribePackets(ctx context.Context, in *SubscribePacketsRequest, opts ...grpc.CallOption) (Relayer_SubscribePacketsClient, error) { 56 | stream, err := c.cc.NewStream(ctx, &Relayer_ServiceDesc.Streams[0], Relayer_SubscribePackets_FullMethodName, opts...) 57 | if err != nil { 58 | return nil, err 59 | } 60 | x := &relayerSubscribePacketsClient{stream} 61 | if err := x.ClientStream.SendMsg(in); err != nil { 62 | return nil, err 63 | } 64 | if err := x.ClientStream.CloseSend(); err != nil { 65 | return nil, err 66 | } 67 | return x, nil 68 | } 69 | 70 | type Relayer_SubscribePacketsClient interface { 71 | Recv() (*SubscribePacketsResponse, error) 72 | grpc.ClientStream 73 | } 74 | 75 | type relayerSubscribePacketsClient struct { 76 | grpc.ClientStream 77 | } 78 | 79 | func (x *relayerSubscribePacketsClient) Recv() (*SubscribePacketsResponse, error) { 80 | m := new(SubscribePacketsResponse) 81 | if err := x.ClientStream.RecvMsg(m); err != nil { 82 | return nil, err 83 | } 84 | return m, nil 85 | } 86 | 87 | // RelayerServer is the server API for Relayer service. 88 | // All implementations must embed UnimplementedRelayerServer 89 | // for forward compatibility 90 | type RelayerServer interface { 91 | // The relayer has TPU and TPU forward sockets that validator-watcher can leverage. 92 | // A validator can fetch this config and change its TPU and TPU forward port in gossip. 93 | GetTpuConfigs(context.Context, *GetTpuConfigsRequest) (*GetTpuConfigsResponse, error) 94 | // Validators can subscribe to packets from the relayer and receive a multiplexed signal that contains a mixture 95 | // of packets and heartbeats 96 | SubscribePackets(*SubscribePacketsRequest, Relayer_SubscribePacketsServer) error 97 | mustEmbedUnimplementedRelayerServer() 98 | } 99 | 100 | // UnimplementedRelayerServer must be embedded to have forward compatible implementations. 101 | type UnimplementedRelayerServer struct { 102 | } 103 | 104 | func (UnimplementedRelayerServer) GetTpuConfigs(context.Context, *GetTpuConfigsRequest) (*GetTpuConfigsResponse, error) { 105 | return nil, status.Errorf(codes.Unimplemented, "method GetTpuConfigs not implemented") 106 | } 107 | func (UnimplementedRelayerServer) SubscribePackets(*SubscribePacketsRequest, Relayer_SubscribePacketsServer) error { 108 | return status.Errorf(codes.Unimplemented, "method SubscribePackets not implemented") 109 | } 110 | func (UnimplementedRelayerServer) mustEmbedUnimplementedRelayerServer() {} 111 | 112 | // UnsafeRelayerServer may be embedded to opt out of forward compatibility for this service. 113 | // Use of this interface is not recommended, as added methods to RelayerServer will 114 | // result in compilation errors. 115 | type UnsafeRelayerServer interface { 116 | mustEmbedUnimplementedRelayerServer() 117 | } 118 | 119 | func RegisterRelayerServer(s grpc.ServiceRegistrar, srv RelayerServer) { 120 | s.RegisterService(&Relayer_ServiceDesc, srv) 121 | } 122 | 123 | func _Relayer_GetTpuConfigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 124 | in := new(GetTpuConfigsRequest) 125 | if err := dec(in); err != nil { 126 | return nil, err 127 | } 128 | if interceptor == nil { 129 | return srv.(RelayerServer).GetTpuConfigs(ctx, in) 130 | } 131 | info := &grpc.UnaryServerInfo{ 132 | Server: srv, 133 | FullMethod: Relayer_GetTpuConfigs_FullMethodName, 134 | } 135 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 136 | return srv.(RelayerServer).GetTpuConfigs(ctx, req.(*GetTpuConfigsRequest)) 137 | } 138 | return interceptor(ctx, in, info, handler) 139 | } 140 | 141 | func _Relayer_SubscribePackets_Handler(srv interface{}, stream grpc.ServerStream) error { 142 | m := new(SubscribePacketsRequest) 143 | if err := stream.RecvMsg(m); err != nil { 144 | return err 145 | } 146 | return srv.(RelayerServer).SubscribePackets(m, &relayerSubscribePacketsServer{stream}) 147 | } 148 | 149 | type Relayer_SubscribePacketsServer interface { 150 | Send(*SubscribePacketsResponse) error 151 | grpc.ServerStream 152 | } 153 | 154 | type relayerSubscribePacketsServer struct { 155 | grpc.ServerStream 156 | } 157 | 158 | func (x *relayerSubscribePacketsServer) Send(m *SubscribePacketsResponse) error { 159 | return x.ServerStream.SendMsg(m) 160 | } 161 | 162 | // Relayer_ServiceDesc is the grpc.ServiceDesc for Relayer service. 163 | // It's only intended for direct use with grpc.RegisterService, 164 | // and not to be introspected or modified (even as a copy) 165 | var Relayer_ServiceDesc = grpc.ServiceDesc{ 166 | ServiceName: "relayer.Relayer", 167 | HandlerType: (*RelayerServer)(nil), 168 | Methods: []grpc.MethodDesc{ 169 | { 170 | MethodName: "GetTpuConfigs", 171 | Handler: _Relayer_GetTpuConfigs_Handler, 172 | }, 173 | }, 174 | Streams: []grpc.StreamDesc{ 175 | { 176 | StreamName: "SubscribePackets", 177 | Handler: _Relayer_SubscribePackets_Handler, 178 | ServerStreams: true, 179 | }, 180 | }, 181 | Metadata: "relayer.proto", 182 | } 183 | -------------------------------------------------------------------------------- /pkg/jito-go/proto/shared.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.32.0 4 | // protoc v4.25.2 5 | // source: shared.proto 6 | 7 | package proto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type Header struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | Ts *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=ts,proto3" json:"ts,omitempty"` 30 | } 31 | 32 | func (x *Header) Reset() { 33 | *x = Header{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_shared_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *Header) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*Header) ProtoMessage() {} 46 | 47 | func (x *Header) ProtoReflect() protoreflect.Message { 48 | mi := &file_shared_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use Header.ProtoReflect.Descriptor instead. 60 | func (*Header) Descriptor() ([]byte, []int) { 61 | return file_shared_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *Header) GetTs() *timestamppb.Timestamp { 65 | if x != nil { 66 | return x.Ts 67 | } 68 | return nil 69 | } 70 | 71 | type Heartbeat struct { 72 | state protoimpl.MessageState 73 | sizeCache protoimpl.SizeCache 74 | unknownFields protoimpl.UnknownFields 75 | 76 | Count uint64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` 77 | } 78 | 79 | func (x *Heartbeat) Reset() { 80 | *x = Heartbeat{} 81 | if protoimpl.UnsafeEnabled { 82 | mi := &file_shared_proto_msgTypes[1] 83 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 84 | ms.StoreMessageInfo(mi) 85 | } 86 | } 87 | 88 | func (x *Heartbeat) String() string { 89 | return protoimpl.X.MessageStringOf(x) 90 | } 91 | 92 | func (*Heartbeat) ProtoMessage() {} 93 | 94 | func (x *Heartbeat) ProtoReflect() protoreflect.Message { 95 | mi := &file_shared_proto_msgTypes[1] 96 | if protoimpl.UnsafeEnabled && x != nil { 97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 98 | if ms.LoadMessageInfo() == nil { 99 | ms.StoreMessageInfo(mi) 100 | } 101 | return ms 102 | } 103 | return mi.MessageOf(x) 104 | } 105 | 106 | // Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead. 107 | func (*Heartbeat) Descriptor() ([]byte, []int) { 108 | return file_shared_proto_rawDescGZIP(), []int{1} 109 | } 110 | 111 | func (x *Heartbeat) GetCount() uint64 { 112 | if x != nil { 113 | return x.Count 114 | } 115 | return 0 116 | } 117 | 118 | type Socket struct { 119 | state protoimpl.MessageState 120 | sizeCache protoimpl.SizeCache 121 | unknownFields protoimpl.UnknownFields 122 | 123 | Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` 124 | Port int64 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` 125 | } 126 | 127 | func (x *Socket) Reset() { 128 | *x = Socket{} 129 | if protoimpl.UnsafeEnabled { 130 | mi := &file_shared_proto_msgTypes[2] 131 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 132 | ms.StoreMessageInfo(mi) 133 | } 134 | } 135 | 136 | func (x *Socket) String() string { 137 | return protoimpl.X.MessageStringOf(x) 138 | } 139 | 140 | func (*Socket) ProtoMessage() {} 141 | 142 | func (x *Socket) ProtoReflect() protoreflect.Message { 143 | mi := &file_shared_proto_msgTypes[2] 144 | if protoimpl.UnsafeEnabled && x != nil { 145 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 146 | if ms.LoadMessageInfo() == nil { 147 | ms.StoreMessageInfo(mi) 148 | } 149 | return ms 150 | } 151 | return mi.MessageOf(x) 152 | } 153 | 154 | // Deprecated: Use Socket.ProtoReflect.Descriptor instead. 155 | func (*Socket) Descriptor() ([]byte, []int) { 156 | return file_shared_proto_rawDescGZIP(), []int{2} 157 | } 158 | 159 | func (x *Socket) GetIp() string { 160 | if x != nil { 161 | return x.Ip 162 | } 163 | return "" 164 | } 165 | 166 | func (x *Socket) GetPort() int64 { 167 | if x != nil { 168 | return x.Port 169 | } 170 | return 0 171 | } 172 | 173 | var File_shared_proto protoreflect.FileDescriptor 174 | 175 | var file_shared_proto_rawDesc = []byte{ 176 | 0x0a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 177 | 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 178 | 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 179 | 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x34, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 180 | 0x72, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 181 | 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 182 | 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x74, 0x73, 0x22, 0x21, 0x0a, 183 | 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 184 | 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 185 | 0x22, 0x2c, 0x0a, 0x06, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 186 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 187 | 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x04, 188 | 0x5a, 0x02, 0x2e, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 189 | } 190 | 191 | var ( 192 | file_shared_proto_rawDescOnce sync.Once 193 | file_shared_proto_rawDescData = file_shared_proto_rawDesc 194 | ) 195 | 196 | func file_shared_proto_rawDescGZIP() []byte { 197 | file_shared_proto_rawDescOnce.Do(func() { 198 | file_shared_proto_rawDescData = protoimpl.X.CompressGZIP(file_shared_proto_rawDescData) 199 | }) 200 | return file_shared_proto_rawDescData 201 | } 202 | 203 | var file_shared_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 204 | var file_shared_proto_goTypes = []interface{}{ 205 | (*Header)(nil), // 0: shared.Header 206 | (*Heartbeat)(nil), // 1: shared.Heartbeat 207 | (*Socket)(nil), // 2: shared.Socket 208 | (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp 209 | } 210 | var file_shared_proto_depIdxs = []int32{ 211 | 3, // 0: shared.Header.ts:type_name -> google.protobuf.Timestamp 212 | 1, // [1:1] is the sub-list for method output_type 213 | 1, // [1:1] is the sub-list for method input_type 214 | 1, // [1:1] is the sub-list for extension type_name 215 | 1, // [1:1] is the sub-list for extension extendee 216 | 0, // [0:1] is the sub-list for field type_name 217 | } 218 | 219 | func init() { file_shared_proto_init() } 220 | func file_shared_proto_init() { 221 | if File_shared_proto != nil { 222 | return 223 | } 224 | if !protoimpl.UnsafeEnabled { 225 | file_shared_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 226 | switch v := v.(*Header); i { 227 | case 0: 228 | return &v.state 229 | case 1: 230 | return &v.sizeCache 231 | case 2: 232 | return &v.unknownFields 233 | default: 234 | return nil 235 | } 236 | } 237 | file_shared_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 238 | switch v := v.(*Heartbeat); i { 239 | case 0: 240 | return &v.state 241 | case 1: 242 | return &v.sizeCache 243 | case 2: 244 | return &v.unknownFields 245 | default: 246 | return nil 247 | } 248 | } 249 | file_shared_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 250 | switch v := v.(*Socket); i { 251 | case 0: 252 | return &v.state 253 | case 1: 254 | return &v.sizeCache 255 | case 2: 256 | return &v.unknownFields 257 | default: 258 | return nil 259 | } 260 | } 261 | } 262 | type x struct{} 263 | out := protoimpl.TypeBuilder{ 264 | File: protoimpl.DescBuilder{ 265 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 266 | RawDescriptor: file_shared_proto_rawDesc, 267 | NumEnums: 0, 268 | NumMessages: 3, 269 | NumExtensions: 0, 270 | NumServices: 0, 271 | }, 272 | GoTypes: file_shared_proto_goTypes, 273 | DependencyIndexes: file_shared_proto_depIdxs, 274 | MessageInfos: file_shared_proto_msgTypes, 275 | }.Build() 276 | File_shared_proto = out.File 277 | file_shared_proto_rawDesc = nil 278 | file_shared_proto_goTypes = nil 279 | file_shared_proto_depIdxs = nil 280 | } 281 | -------------------------------------------------------------------------------- /pkg/jito-go/proto/shredstream.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.32.0 4 | // protoc v4.25.2 5 | // source: shredstream.proto 6 | 7 | package proto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type HeartbeatShredStream struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | // don't trust IP:PORT from tcp header since it can be tampered over the wire 29 | // `socket.ip` must match incoming packet's ip. this prevents spamming an unwitting destination 30 | Socket *Socket `protobuf:"bytes,1,opt,name=socket,proto3" json:"socket,omitempty"` 31 | // regions for shredstream proxy to receive shreds from 32 | // list of valid regions: https://jito-labs.gitbook.io/mev/systems/connecting/mainnet 33 | Regions []string `protobuf:"bytes,2,rep,name=regions,proto3" json:"regions,omitempty"` 34 | } 35 | 36 | func (x *HeartbeatShredStream) Reset() { 37 | *x = HeartbeatShredStream{} 38 | if protoimpl.UnsafeEnabled { 39 | mi := &file_shredstream_proto_msgTypes[0] 40 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 41 | ms.StoreMessageInfo(mi) 42 | } 43 | } 44 | 45 | func (x *HeartbeatShredStream) String() string { 46 | return protoimpl.X.MessageStringOf(x) 47 | } 48 | 49 | func (*HeartbeatShredStream) ProtoMessage() {} 50 | 51 | func (x *HeartbeatShredStream) ProtoReflect() protoreflect.Message { 52 | mi := &file_shredstream_proto_msgTypes[0] 53 | if protoimpl.UnsafeEnabled && x != nil { 54 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 55 | if ms.LoadMessageInfo() == nil { 56 | ms.StoreMessageInfo(mi) 57 | } 58 | return ms 59 | } 60 | return mi.MessageOf(x) 61 | } 62 | 63 | // Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead. 64 | func (*HeartbeatShredStream) Descriptor() ([]byte, []int) { 65 | return file_shredstream_proto_rawDescGZIP(), []int{0} 66 | } 67 | 68 | func (x *HeartbeatShredStream) GetSocket() *Socket { 69 | if x != nil { 70 | return x.Socket 71 | } 72 | return nil 73 | } 74 | 75 | func (x *HeartbeatShredStream) GetRegions() []string { 76 | if x != nil { 77 | return x.Regions 78 | } 79 | return nil 80 | } 81 | 82 | type HeartbeatResponse struct { 83 | state protoimpl.MessageState 84 | sizeCache protoimpl.SizeCache 85 | unknownFields protoimpl.UnknownFields 86 | 87 | // client must respond within `ttl_ms` to keep stream alive 88 | TtlMs uint32 `protobuf:"varint,1,opt,name=ttl_ms,json=ttlMs,proto3" json:"ttl_ms,omitempty"` 89 | } 90 | 91 | func (x *HeartbeatResponse) Reset() { 92 | *x = HeartbeatResponse{} 93 | if protoimpl.UnsafeEnabled { 94 | mi := &file_shredstream_proto_msgTypes[1] 95 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 96 | ms.StoreMessageInfo(mi) 97 | } 98 | } 99 | 100 | func (x *HeartbeatResponse) String() string { 101 | return protoimpl.X.MessageStringOf(x) 102 | } 103 | 104 | func (*HeartbeatResponse) ProtoMessage() {} 105 | 106 | func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { 107 | mi := &file_shredstream_proto_msgTypes[1] 108 | if protoimpl.UnsafeEnabled && x != nil { 109 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 110 | if ms.LoadMessageInfo() == nil { 111 | ms.StoreMessageInfo(mi) 112 | } 113 | return ms 114 | } 115 | return mi.MessageOf(x) 116 | } 117 | 118 | // Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. 119 | func (*HeartbeatResponse) Descriptor() ([]byte, []int) { 120 | return file_shredstream_proto_rawDescGZIP(), []int{1} 121 | } 122 | 123 | func (x *HeartbeatResponse) GetTtlMs() uint32 { 124 | if x != nil { 125 | return x.TtlMs 126 | } 127 | return 0 128 | } 129 | 130 | var File_shredstream_proto protoreflect.FileDescriptor 131 | 132 | var file_shredstream_proto_rawDesc = []byte{ 133 | 0x0a, 0x11, 0x73, 0x68, 0x72, 0x65, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 134 | 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x68, 0x72, 0x65, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 135 | 0x1a, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4d, 136 | 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x73, 137 | 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x68, 138 | 0x61, 0x72, 0x65, 0x64, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x06, 0x73, 0x6f, 0x63, 139 | 0x6b, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 140 | 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x2a, 0x0a, 141 | 0x11, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 142 | 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x74, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 143 | 0x28, 0x0d, 0x52, 0x05, 0x74, 0x74, 0x6c, 0x4d, 0x73, 0x32, 0x58, 0x0a, 0x0b, 0x53, 0x68, 0x72, 144 | 0x65, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x49, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 145 | 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x16, 0x2e, 0x73, 0x68, 0x72, 0x65, 146 | 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 147 | 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x68, 0x72, 0x65, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 148 | 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 149 | 0x65, 0x22, 0x00, 0x42, 0x04, 0x5a, 0x02, 0x2e, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 150 | 0x33, 151 | } 152 | 153 | var ( 154 | file_shredstream_proto_rawDescOnce sync.Once 155 | file_shredstream_proto_rawDescData = file_shredstream_proto_rawDesc 156 | ) 157 | 158 | func file_shredstream_proto_rawDescGZIP() []byte { 159 | file_shredstream_proto_rawDescOnce.Do(func() { 160 | file_shredstream_proto_rawDescData = protoimpl.X.CompressGZIP(file_shredstream_proto_rawDescData) 161 | }) 162 | return file_shredstream_proto_rawDescData 163 | } 164 | 165 | var file_shredstream_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 166 | var file_shredstream_proto_goTypes = []interface{}{ 167 | (*Heartbeat)(nil), // 0: shredstream.Heartbeat 168 | (*HeartbeatResponse)(nil), // 1: shredstream.HeartbeatResponse 169 | (*Socket)(nil), // 2: shared.Socket 170 | } 171 | var file_shredstream_proto_depIdxs = []int32{ 172 | 2, // 0: shredstream.Heartbeat.socket:type_name -> shared.Socket 173 | 0, // 1: shredstream.Shredstream.SendHeartbeat:input_type -> shredstream.Heartbeat 174 | 1, // 2: shredstream.Shredstream.SendHeartbeat:output_type -> shredstream.HeartbeatResponse 175 | 2, // [2:3] is the sub-list for method output_type 176 | 1, // [1:2] is the sub-list for method input_type 177 | 1, // [1:1] is the sub-list for extension type_name 178 | 1, // [1:1] is the sub-list for extension extendee 179 | 0, // [0:1] is the sub-list for field type_name 180 | } 181 | 182 | func init() { file_shredstream_proto_init() } 183 | func file_shredstream_proto_init() { 184 | if File_shredstream_proto != nil { 185 | return 186 | } 187 | file_shared_proto_init() 188 | if !protoimpl.UnsafeEnabled { 189 | file_shredstream_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 190 | switch v := v.(*Heartbeat); i { 191 | case 0: 192 | return &v.state 193 | case 1: 194 | return &v.sizeCache 195 | case 2: 196 | return &v.unknownFields 197 | default: 198 | return nil 199 | } 200 | } 201 | file_shredstream_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 202 | switch v := v.(*HeartbeatResponse); i { 203 | case 0: 204 | return &v.state 205 | case 1: 206 | return &v.sizeCache 207 | case 2: 208 | return &v.unknownFields 209 | default: 210 | return nil 211 | } 212 | } 213 | } 214 | type x struct{} 215 | out := protoimpl.TypeBuilder{ 216 | File: protoimpl.DescBuilder{ 217 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 218 | RawDescriptor: file_shredstream_proto_rawDesc, 219 | NumEnums: 0, 220 | NumMessages: 2, 221 | NumExtensions: 0, 222 | NumServices: 1, 223 | }, 224 | GoTypes: file_shredstream_proto_goTypes, 225 | DependencyIndexes: file_shredstream_proto_depIdxs, 226 | MessageInfos: file_shredstream_proto_msgTypes, 227 | }.Build() 228 | File_shredstream_proto = out.File 229 | file_shredstream_proto_rawDesc = nil 230 | file_shredstream_proto_goTypes = nil 231 | file_shredstream_proto_depIdxs = nil 232 | } 233 | -------------------------------------------------------------------------------- /pkg/jito-go/proto/shredstream_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc v4.25.2 5 | // source: shredstream.proto 6 | 7 | package proto 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Shredstream_SendHeartbeat_FullMethodName = "/shredstream.Shredstream/SendHeartbeat" 23 | ) 24 | 25 | // ShredstreamClient is the client API for Shredstream service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type ShredstreamClient interface { 29 | // RPC endpoint to send heartbeats to keep shreds flowing 30 | SendHeartbeat(ctx context.Context, in *Heartbeat, opts ...grpc.CallOption) (*HeartbeatResponse, error) 31 | } 32 | 33 | type shredstreamClient struct { 34 | cc grpc.ClientConnInterface 35 | } 36 | 37 | func NewShredstreamClient(cc grpc.ClientConnInterface) ShredstreamClient { 38 | return &shredstreamClient{cc} 39 | } 40 | 41 | func (c *shredstreamClient) SendHeartbeat(ctx context.Context, in *Heartbeat, opts ...grpc.CallOption) (*HeartbeatResponse, error) { 42 | out := new(HeartbeatResponse) 43 | err := c.cc.Invoke(ctx, Shredstream_SendHeartbeat_FullMethodName, in, out, opts...) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return out, nil 48 | } 49 | 50 | // ShredstreamServer is the server API for Shredstream service. 51 | // All implementations must embed UnimplementedShredstreamServer 52 | // for forward compatibility 53 | type ShredstreamServer interface { 54 | // RPC endpoint to send heartbeats to keep shreds flowing 55 | SendHeartbeat(context.Context, *Heartbeat) (*HeartbeatResponse, error) 56 | mustEmbedUnimplementedShredstreamServer() 57 | } 58 | 59 | // UnimplementedShredstreamServer must be embedded to have forward compatible implementations. 60 | type UnimplementedShredstreamServer struct { 61 | } 62 | 63 | func (UnimplementedShredstreamServer) SendHeartbeat(context.Context, *Heartbeat) (*HeartbeatResponse, error) { 64 | return nil, status.Errorf(codes.Unimplemented, "method SendHeartbeat not implemented") 65 | } 66 | func (UnimplementedShredstreamServer) mustEmbedUnimplementedShredstreamServer() {} 67 | 68 | // UnsafeShredstreamServer may be embedded to opt out of forward compatibility for this service. 69 | // Use of this interface is not recommended, as added methods to ShredstreamServer will 70 | // result in compilation errors. 71 | type UnsafeShredstreamServer interface { 72 | mustEmbedUnimplementedShredstreamServer() 73 | } 74 | 75 | func RegisterShredstreamServer(s grpc.ServiceRegistrar, srv ShredstreamServer) { 76 | s.RegisterService(&Shredstream_ServiceDesc, srv) 77 | } 78 | 79 | func _Shredstream_SendHeartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 80 | in := new(Heartbeat) 81 | if err := dec(in); err != nil { 82 | return nil, err 83 | } 84 | if interceptor == nil { 85 | return srv.(ShredstreamServer).SendHeartbeat(ctx, in) 86 | } 87 | info := &grpc.UnaryServerInfo{ 88 | Server: srv, 89 | FullMethod: Shredstream_SendHeartbeat_FullMethodName, 90 | } 91 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 92 | return srv.(ShredstreamServer).SendHeartbeat(ctx, req.(*Heartbeat)) 93 | } 94 | return interceptor(ctx, in, info, handler) 95 | } 96 | 97 | // Shredstream_ServiceDesc is the grpc.ServiceDesc for Shredstream service. 98 | // It's only intended for direct use with grpc.RegisterService, 99 | // and not to be introspected or modified (even as a copy) 100 | var Shredstream_ServiceDesc = grpc.ServiceDesc{ 101 | ServiceName: "shredstream.Shredstream", 102 | HandlerType: (*ShredstreamServer)(nil), 103 | Methods: []grpc.MethodDesc{ 104 | { 105 | MethodName: "SendHeartbeat", 106 | Handler: _Shredstream_SendHeartbeat_Handler, 107 | }, 108 | }, 109 | Streams: []grpc.StreamDesc{}, 110 | Metadata: "shredstream.proto", 111 | } 112 | -------------------------------------------------------------------------------- /pkg/jito-go/proto/trace_shred.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.32.0 4 | // protoc v4.25.2 5 | // source: trace_shred.proto 6 | 7 | package proto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type TraceShred struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | // source region, one of: https://jito-labs.gitbook.io/mev/systems/connecting/mainnet 30 | Region string `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` 31 | // timestamp of creation 32 | CreatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` 33 | // monotonically increases, resets upon service restart 34 | SeqNum uint32 `protobuf:"varint,3,opt,name=seq_num,json=seqNum,proto3" json:"seq_num,omitempty"` 35 | } 36 | 37 | func (x *TraceShred) Reset() { 38 | *x = TraceShred{} 39 | if protoimpl.UnsafeEnabled { 40 | mi := &file_trace_shred_proto_msgTypes[0] 41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 | ms.StoreMessageInfo(mi) 43 | } 44 | } 45 | 46 | func (x *TraceShred) String() string { 47 | return protoimpl.X.MessageStringOf(x) 48 | } 49 | 50 | func (*TraceShred) ProtoMessage() {} 51 | 52 | func (x *TraceShred) ProtoReflect() protoreflect.Message { 53 | mi := &file_trace_shred_proto_msgTypes[0] 54 | if protoimpl.UnsafeEnabled && x != nil { 55 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 56 | if ms.LoadMessageInfo() == nil { 57 | ms.StoreMessageInfo(mi) 58 | } 59 | return ms 60 | } 61 | return mi.MessageOf(x) 62 | } 63 | 64 | // Deprecated: Use TraceShred.ProtoReflect.Descriptor instead. 65 | func (*TraceShred) Descriptor() ([]byte, []int) { 66 | return file_trace_shred_proto_rawDescGZIP(), []int{0} 67 | } 68 | 69 | func (x *TraceShred) GetRegion() string { 70 | if x != nil { 71 | return x.Region 72 | } 73 | return "" 74 | } 75 | 76 | func (x *TraceShred) GetCreatedAt() *timestamppb.Timestamp { 77 | if x != nil { 78 | return x.CreatedAt 79 | } 80 | return nil 81 | } 82 | 83 | func (x *TraceShred) GetSeqNum() uint32 { 84 | if x != nil { 85 | return x.SeqNum 86 | } 87 | return 0 88 | } 89 | 90 | var File_trace_shred_proto protoreflect.FileDescriptor 91 | 92 | var file_trace_shred_proto_rawDesc = []byte{ 93 | 0x0a, 0x11, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x68, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 94 | 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x73, 0x68, 0x72, 0x65, 0x64, 95 | 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 96 | 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 97 | 0x6f, 0x22, 0x78, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x68, 0x72, 0x65, 0x64, 0x12, 98 | 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 99 | 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 100 | 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 101 | 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 102 | 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 103 | 0x41, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 104 | 0x01, 0x28, 0x0d, 0x52, 0x06, 0x73, 0x65, 0x71, 0x4e, 0x75, 0x6d, 0x42, 0x04, 0x5a, 0x02, 0x2e, 105 | 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 106 | } 107 | 108 | var ( 109 | file_trace_shred_proto_rawDescOnce sync.Once 110 | file_trace_shred_proto_rawDescData = file_trace_shred_proto_rawDesc 111 | ) 112 | 113 | func file_trace_shred_proto_rawDescGZIP() []byte { 114 | file_trace_shred_proto_rawDescOnce.Do(func() { 115 | file_trace_shred_proto_rawDescData = protoimpl.X.CompressGZIP(file_trace_shred_proto_rawDescData) 116 | }) 117 | return file_trace_shred_proto_rawDescData 118 | } 119 | 120 | var file_trace_shred_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 121 | var file_trace_shred_proto_goTypes = []interface{}{ 122 | (*TraceShred)(nil), // 0: trace_shred.TraceShred 123 | (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp 124 | } 125 | var file_trace_shred_proto_depIdxs = []int32{ 126 | 1, // 0: trace_shred.TraceShred.created_at:type_name -> google.protobuf.Timestamp 127 | 1, // [1:1] is the sub-list for method output_type 128 | 1, // [1:1] is the sub-list for method input_type 129 | 1, // [1:1] is the sub-list for extension type_name 130 | 1, // [1:1] is the sub-list for extension extendee 131 | 0, // [0:1] is the sub-list for field type_name 132 | } 133 | 134 | func init() { file_trace_shred_proto_init() } 135 | func file_trace_shred_proto_init() { 136 | if File_trace_shred_proto != nil { 137 | return 138 | } 139 | if !protoimpl.UnsafeEnabled { 140 | file_trace_shred_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 141 | switch v := v.(*TraceShred); i { 142 | case 0: 143 | return &v.state 144 | case 1: 145 | return &v.sizeCache 146 | case 2: 147 | return &v.unknownFields 148 | default: 149 | return nil 150 | } 151 | } 152 | } 153 | type x struct{} 154 | out := protoimpl.TypeBuilder{ 155 | File: protoimpl.DescBuilder{ 156 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 157 | RawDescriptor: file_trace_shred_proto_rawDesc, 158 | NumEnums: 0, 159 | NumMessages: 1, 160 | NumExtensions: 0, 161 | NumServices: 0, 162 | }, 163 | GoTypes: file_trace_shred_proto_goTypes, 164 | DependencyIndexes: file_trace_shred_proto_depIdxs, 165 | MessageInfos: file_trace_shred_proto_msgTypes, 166 | }.Build() 167 | File_trace_shred_proto = out.File 168 | file_trace_shred_proto_rawDesc = nil 169 | file_trace_shred_proto_goTypes = nil 170 | file_trace_shred_proto_depIdxs = nil 171 | } 172 | -------------------------------------------------------------------------------- /pkg/jito-go/scripts/run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | set "ENV_FILE=.env" 5 | set "PRIVATE_KEY_VALUE=YourPrivateKeyHere" 6 | 7 | :: Check if the .env file already exists 8 | if exist "%ENV_FILE%" ( 9 | echo %ENV_FILE% exists, appending to it. 10 | ) else ( 11 | echo %ENV_FILE% does not exist, creating it. 12 | type nul > "%ENV_FILE%" 13 | ) 14 | 15 | :: Append the PRIVATE_KEY variable to the .env file 16 | echo PRIVATE_KEY= >> "%ENV_FILE%" 17 | echo JITO_ROC= >> "%ENV_FILE%" 18 | echo GEYSER_RPC= >> "%ENV_FILE%" 19 | echo PRIVATE_KEY_WITH_FUNDS= >> "%ENV_FILE% 20 | 21 | endlocal 22 | -------------------------------------------------------------------------------- /pkg/jito-go/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the path of the .env file 4 | ENV_FILE=".env" 5 | 6 | # Check if the .env file already exists 7 | if [ -f "$ENV_FILE" ]; then 8 | echo "$ENV_FILE exists, appending to it." 9 | else 10 | echo "$ENV_FILE does not exist, creating it." 11 | touch "$ENV_FILE" 12 | fi 13 | 14 | echo "PRIVATE_KEY=" >> "$ENV_FILE" 15 | echo "JITO_RPC=" >> "$ENV_FILE" 16 | echo "GEYSER_RPC=" >> "$ENV_FILE" 17 | echo "PRIVATE_KEY_WITH_FUNDS=" >> "$ENV_FILE" -------------------------------------------------------------------------------- /pkg/jito-go/taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | tasks: 4 | install:linux: 5 | cmds: 6 | - sh scripts/run.sh 7 | install:darwin: 8 | cmds: 9 | - sh scripts/run.sh 10 | install:windows: 11 | cmds: 12 | - cmd /c scripts\run.bat 13 | test: 14 | cmds: 15 | - go test ./... -------------------------------------------------------------------------------- /pump/Buy_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_Buy(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("Buy"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(Buy) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(Buy) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pump/Create_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_Create(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("Create"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(Create) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(Create) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pump/Initialize.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "errors" 7 | ag_binary "github.com/gagliardetto/binary" 8 | ag_solanago "github.com/gagliardetto/solana-go" 9 | ag_format "github.com/gagliardetto/solana-go/text/format" 10 | ag_treeout "github.com/gagliardetto/treeout" 11 | ) 12 | 13 | // Creates the global state. 14 | type Initialize struct { 15 | 16 | // [0] = [WRITE] global 17 | // 18 | // [1] = [WRITE, SIGNER] user 19 | // 20 | // [2] = [] systemProgram 21 | ag_solanago.AccountMetaSlice `bin:"-"` 22 | } 23 | 24 | // NewInitializeInstructionBuilder creates a new `Initialize` instruction builder. 25 | func NewInitializeInstructionBuilder() *Initialize { 26 | nd := &Initialize{ 27 | AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), 28 | } 29 | return nd 30 | } 31 | 32 | // SetGlobalAccount sets the "global" account. 33 | func (inst *Initialize) SetGlobalAccount(global ag_solanago.PublicKey) *Initialize { 34 | inst.AccountMetaSlice[0] = ag_solanago.Meta(global).WRITE() 35 | return inst 36 | } 37 | 38 | // GetGlobalAccount gets the "global" account. 39 | func (inst *Initialize) GetGlobalAccount() *ag_solanago.AccountMeta { 40 | return inst.AccountMetaSlice.Get(0) 41 | } 42 | 43 | // SetUserAccount sets the "user" account. 44 | func (inst *Initialize) SetUserAccount(user ag_solanago.PublicKey) *Initialize { 45 | inst.AccountMetaSlice[1] = ag_solanago.Meta(user).WRITE().SIGNER() 46 | return inst 47 | } 48 | 49 | // GetUserAccount gets the "user" account. 50 | func (inst *Initialize) GetUserAccount() *ag_solanago.AccountMeta { 51 | return inst.AccountMetaSlice.Get(1) 52 | } 53 | 54 | // SetSystemProgramAccount sets the "systemProgram" account. 55 | func (inst *Initialize) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initialize { 56 | inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) 57 | return inst 58 | } 59 | 60 | // GetSystemProgramAccount gets the "systemProgram" account. 61 | func (inst *Initialize) GetSystemProgramAccount() *ag_solanago.AccountMeta { 62 | return inst.AccountMetaSlice.Get(2) 63 | } 64 | 65 | func (inst Initialize) Build() *Instruction { 66 | return &Instruction{BaseVariant: ag_binary.BaseVariant{ 67 | Impl: inst, 68 | TypeID: Instruction_Initialize, 69 | }} 70 | } 71 | 72 | // ValidateAndBuild validates the instruction parameters and accounts; 73 | // if there is a validation error, it returns the error. 74 | // Otherwise, it builds and returns the instruction. 75 | func (inst Initialize) ValidateAndBuild() (*Instruction, error) { 76 | if err := inst.Validate(); err != nil { 77 | return nil, err 78 | } 79 | return inst.Build(), nil 80 | } 81 | 82 | func (inst *Initialize) Validate() error { 83 | // Check whether all (required) accounts are set: 84 | { 85 | if inst.AccountMetaSlice[0] == nil { 86 | return errors.New("accounts.Global is not set") 87 | } 88 | if inst.AccountMetaSlice[1] == nil { 89 | return errors.New("accounts.User is not set") 90 | } 91 | if inst.AccountMetaSlice[2] == nil { 92 | return errors.New("accounts.SystemProgram is not set") 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | func (inst *Initialize) EncodeToTree(parent ag_treeout.Branches) { 99 | parent.Child(ag_format.Program(ProgramName, ProgramID)). 100 | // 101 | ParentFunc(func(programBranch ag_treeout.Branches) { 102 | programBranch.Child(ag_format.Instruction("Initialize")). 103 | // 104 | ParentFunc(func(instructionBranch ag_treeout.Branches) { 105 | 106 | // Parameters of the instruction: 107 | instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) 108 | 109 | // Accounts of the instruction: 110 | instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { 111 | accountsBranch.Child(ag_format.Meta(" global", inst.AccountMetaSlice.Get(0))) 112 | accountsBranch.Child(ag_format.Meta(" user", inst.AccountMetaSlice.Get(1))) 113 | accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice.Get(2))) 114 | }) 115 | }) 116 | }) 117 | } 118 | 119 | func (obj Initialize) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 120 | return nil 121 | } 122 | func (obj *Initialize) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 123 | return nil 124 | } 125 | 126 | // NewInitializeInstruction declares a new Initialize instruction with the provided parameters and accounts. 127 | func NewInitializeInstruction( 128 | // Accounts: 129 | global ag_solanago.PublicKey, 130 | user ag_solanago.PublicKey, 131 | systemProgram ag_solanago.PublicKey) *Initialize { 132 | return NewInitializeInstructionBuilder(). 133 | SetGlobalAccount(global). 134 | SetUserAccount(user). 135 | SetSystemProgramAccount(systemProgram) 136 | } 137 | -------------------------------------------------------------------------------- /pump/Initialize_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_Initialize(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("Initialize"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(Initialize) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(Initialize) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pump/Sell_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_Sell(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("Sell"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(Sell) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(Sell) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pump/SetParams.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "errors" 7 | ag_binary "github.com/gagliardetto/binary" 8 | ag_solanago "github.com/gagliardetto/solana-go" 9 | ag_format "github.com/gagliardetto/solana-go/text/format" 10 | ag_treeout "github.com/gagliardetto/treeout" 11 | ) 12 | 13 | // Sets the global state parameters. 14 | type SetParams struct { 15 | FeeRecipient *ag_solanago.PublicKey 16 | InitialVirtualTokenReserves *uint64 17 | InitialVirtualSolReserves *uint64 18 | InitialRealTokenReserves *uint64 19 | TokenTotalSupply *uint64 20 | FeeBasisPoints *uint64 21 | 22 | // [0] = [WRITE] global 23 | // 24 | // [1] = [WRITE, SIGNER] user 25 | // 26 | // [2] = [] systemProgram 27 | // 28 | // [3] = [] eventAuthority 29 | // 30 | // [4] = [] program 31 | ag_solanago.AccountMetaSlice `bin:"-"` 32 | } 33 | 34 | // NewSetParamsInstructionBuilder creates a new `SetParams` instruction builder. 35 | func NewSetParamsInstructionBuilder() *SetParams { 36 | nd := &SetParams{ 37 | AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 5), 38 | } 39 | return nd 40 | } 41 | 42 | // SetFeeRecipient sets the "feeRecipient" parameter. 43 | func (inst *SetParams) SetFeeRecipient(feeRecipient ag_solanago.PublicKey) *SetParams { 44 | inst.FeeRecipient = &feeRecipient 45 | return inst 46 | } 47 | 48 | // SetInitialVirtualTokenReserves sets the "initialVirtualTokenReserves" parameter. 49 | func (inst *SetParams) SetInitialVirtualTokenReserves(initialVirtualTokenReserves uint64) *SetParams { 50 | inst.InitialVirtualTokenReserves = &initialVirtualTokenReserves 51 | return inst 52 | } 53 | 54 | // SetInitialVirtualSolReserves sets the "initialVirtualSolReserves" parameter. 55 | func (inst *SetParams) SetInitialVirtualSolReserves(initialVirtualSolReserves uint64) *SetParams { 56 | inst.InitialVirtualSolReserves = &initialVirtualSolReserves 57 | return inst 58 | } 59 | 60 | // SetInitialRealTokenReserves sets the "initialRealTokenReserves" parameter. 61 | func (inst *SetParams) SetInitialRealTokenReserves(initialRealTokenReserves uint64) *SetParams { 62 | inst.InitialRealTokenReserves = &initialRealTokenReserves 63 | return inst 64 | } 65 | 66 | // SetTokenTotalSupply sets the "tokenTotalSupply" parameter. 67 | func (inst *SetParams) SetTokenTotalSupply(tokenTotalSupply uint64) *SetParams { 68 | inst.TokenTotalSupply = &tokenTotalSupply 69 | return inst 70 | } 71 | 72 | // SetFeeBasisPoints sets the "feeBasisPoints" parameter. 73 | func (inst *SetParams) SetFeeBasisPoints(feeBasisPoints uint64) *SetParams { 74 | inst.FeeBasisPoints = &feeBasisPoints 75 | return inst 76 | } 77 | 78 | // SetGlobalAccount sets the "global" account. 79 | func (inst *SetParams) SetGlobalAccount(global ag_solanago.PublicKey) *SetParams { 80 | inst.AccountMetaSlice[0] = ag_solanago.Meta(global).WRITE() 81 | return inst 82 | } 83 | 84 | // GetGlobalAccount gets the "global" account. 85 | func (inst *SetParams) GetGlobalAccount() *ag_solanago.AccountMeta { 86 | return inst.AccountMetaSlice.Get(0) 87 | } 88 | 89 | // SetUserAccount sets the "user" account. 90 | func (inst *SetParams) SetUserAccount(user ag_solanago.PublicKey) *SetParams { 91 | inst.AccountMetaSlice[1] = ag_solanago.Meta(user).WRITE().SIGNER() 92 | return inst 93 | } 94 | 95 | // GetUserAccount gets the "user" account. 96 | func (inst *SetParams) GetUserAccount() *ag_solanago.AccountMeta { 97 | return inst.AccountMetaSlice.Get(1) 98 | } 99 | 100 | // SetSystemProgramAccount sets the "systemProgram" account. 101 | func (inst *SetParams) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *SetParams { 102 | inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) 103 | return inst 104 | } 105 | 106 | // GetSystemProgramAccount gets the "systemProgram" account. 107 | func (inst *SetParams) GetSystemProgramAccount() *ag_solanago.AccountMeta { 108 | return inst.AccountMetaSlice.Get(2) 109 | } 110 | 111 | // SetEventAuthorityAccount sets the "eventAuthority" account. 112 | func (inst *SetParams) SetEventAuthorityAccount(eventAuthority ag_solanago.PublicKey) *SetParams { 113 | inst.AccountMetaSlice[3] = ag_solanago.Meta(eventAuthority) 114 | return inst 115 | } 116 | 117 | // GetEventAuthorityAccount gets the "eventAuthority" account. 118 | func (inst *SetParams) GetEventAuthorityAccount() *ag_solanago.AccountMeta { 119 | return inst.AccountMetaSlice.Get(3) 120 | } 121 | 122 | // SetProgramAccount sets the "program" account. 123 | func (inst *SetParams) SetProgramAccount(program ag_solanago.PublicKey) *SetParams { 124 | inst.AccountMetaSlice[4] = ag_solanago.Meta(program) 125 | return inst 126 | } 127 | 128 | // GetProgramAccount gets the "program" account. 129 | func (inst *SetParams) GetProgramAccount() *ag_solanago.AccountMeta { 130 | return inst.AccountMetaSlice.Get(4) 131 | } 132 | 133 | func (inst SetParams) Build() *Instruction { 134 | return &Instruction{BaseVariant: ag_binary.BaseVariant{ 135 | Impl: inst, 136 | TypeID: Instruction_SetParams, 137 | }} 138 | } 139 | 140 | // ValidateAndBuild validates the instruction parameters and accounts; 141 | // if there is a validation error, it returns the error. 142 | // Otherwise, it builds and returns the instruction. 143 | func (inst SetParams) ValidateAndBuild() (*Instruction, error) { 144 | if err := inst.Validate(); err != nil { 145 | return nil, err 146 | } 147 | return inst.Build(), nil 148 | } 149 | 150 | func (inst *SetParams) Validate() error { 151 | // Check whether all (required) parameters are set: 152 | { 153 | if inst.FeeRecipient == nil { 154 | return errors.New("FeeRecipient parameter is not set") 155 | } 156 | if inst.InitialVirtualTokenReserves == nil { 157 | return errors.New("InitialVirtualTokenReserves parameter is not set") 158 | } 159 | if inst.InitialVirtualSolReserves == nil { 160 | return errors.New("InitialVirtualSolReserves parameter is not set") 161 | } 162 | if inst.InitialRealTokenReserves == nil { 163 | return errors.New("InitialRealTokenReserves parameter is not set") 164 | } 165 | if inst.TokenTotalSupply == nil { 166 | return errors.New("TokenTotalSupply parameter is not set") 167 | } 168 | if inst.FeeBasisPoints == nil { 169 | return errors.New("FeeBasisPoints parameter is not set") 170 | } 171 | } 172 | 173 | // Check whether all (required) accounts are set: 174 | { 175 | if inst.AccountMetaSlice[0] == nil { 176 | return errors.New("accounts.Global is not set") 177 | } 178 | if inst.AccountMetaSlice[1] == nil { 179 | return errors.New("accounts.User is not set") 180 | } 181 | if inst.AccountMetaSlice[2] == nil { 182 | return errors.New("accounts.SystemProgram is not set") 183 | } 184 | if inst.AccountMetaSlice[3] == nil { 185 | return errors.New("accounts.EventAuthority is not set") 186 | } 187 | if inst.AccountMetaSlice[4] == nil { 188 | return errors.New("accounts.Program is not set") 189 | } 190 | } 191 | return nil 192 | } 193 | 194 | func (inst *SetParams) EncodeToTree(parent ag_treeout.Branches) { 195 | parent.Child(ag_format.Program(ProgramName, ProgramID)). 196 | // 197 | ParentFunc(func(programBranch ag_treeout.Branches) { 198 | programBranch.Child(ag_format.Instruction("SetParams")). 199 | // 200 | ParentFunc(func(instructionBranch ag_treeout.Branches) { 201 | 202 | // Parameters of the instruction: 203 | instructionBranch.Child("Params[len=6]").ParentFunc(func(paramsBranch ag_treeout.Branches) { 204 | paramsBranch.Child(ag_format.Param(" FeeRecipient", *inst.FeeRecipient)) 205 | paramsBranch.Child(ag_format.Param("InitialVirtualTokenReserves", *inst.InitialVirtualTokenReserves)) 206 | paramsBranch.Child(ag_format.Param(" InitialVirtualSolReserves", *inst.InitialVirtualSolReserves)) 207 | paramsBranch.Child(ag_format.Param(" InitialRealTokenReserves", *inst.InitialRealTokenReserves)) 208 | paramsBranch.Child(ag_format.Param(" TokenTotalSupply", *inst.TokenTotalSupply)) 209 | paramsBranch.Child(ag_format.Param(" FeeBasisPoints", *inst.FeeBasisPoints)) 210 | }) 211 | 212 | // Accounts of the instruction: 213 | instructionBranch.Child("Accounts[len=5]").ParentFunc(func(accountsBranch ag_treeout.Branches) { 214 | accountsBranch.Child(ag_format.Meta(" global", inst.AccountMetaSlice.Get(0))) 215 | accountsBranch.Child(ag_format.Meta(" user", inst.AccountMetaSlice.Get(1))) 216 | accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice.Get(2))) 217 | accountsBranch.Child(ag_format.Meta("eventAuthority", inst.AccountMetaSlice.Get(3))) 218 | accountsBranch.Child(ag_format.Meta(" program", inst.AccountMetaSlice.Get(4))) 219 | }) 220 | }) 221 | }) 222 | } 223 | 224 | func (obj SetParams) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 225 | // Serialize `FeeRecipient` param: 226 | err = encoder.Encode(obj.FeeRecipient) 227 | if err != nil { 228 | return err 229 | } 230 | // Serialize `InitialVirtualTokenReserves` param: 231 | err = encoder.Encode(obj.InitialVirtualTokenReserves) 232 | if err != nil { 233 | return err 234 | } 235 | // Serialize `InitialVirtualSolReserves` param: 236 | err = encoder.Encode(obj.InitialVirtualSolReserves) 237 | if err != nil { 238 | return err 239 | } 240 | // Serialize `InitialRealTokenReserves` param: 241 | err = encoder.Encode(obj.InitialRealTokenReserves) 242 | if err != nil { 243 | return err 244 | } 245 | // Serialize `TokenTotalSupply` param: 246 | err = encoder.Encode(obj.TokenTotalSupply) 247 | if err != nil { 248 | return err 249 | } 250 | // Serialize `FeeBasisPoints` param: 251 | err = encoder.Encode(obj.FeeBasisPoints) 252 | if err != nil { 253 | return err 254 | } 255 | return nil 256 | } 257 | func (obj *SetParams) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 258 | // Deserialize `FeeRecipient`: 259 | err = decoder.Decode(&obj.FeeRecipient) 260 | if err != nil { 261 | return err 262 | } 263 | // Deserialize `InitialVirtualTokenReserves`: 264 | err = decoder.Decode(&obj.InitialVirtualTokenReserves) 265 | if err != nil { 266 | return err 267 | } 268 | // Deserialize `InitialVirtualSolReserves`: 269 | err = decoder.Decode(&obj.InitialVirtualSolReserves) 270 | if err != nil { 271 | return err 272 | } 273 | // Deserialize `InitialRealTokenReserves`: 274 | err = decoder.Decode(&obj.InitialRealTokenReserves) 275 | if err != nil { 276 | return err 277 | } 278 | // Deserialize `TokenTotalSupply`: 279 | err = decoder.Decode(&obj.TokenTotalSupply) 280 | if err != nil { 281 | return err 282 | } 283 | // Deserialize `FeeBasisPoints`: 284 | err = decoder.Decode(&obj.FeeBasisPoints) 285 | if err != nil { 286 | return err 287 | } 288 | return nil 289 | } 290 | 291 | // NewSetParamsInstruction declares a new SetParams instruction with the provided parameters and accounts. 292 | func NewSetParamsInstruction( 293 | // Parameters: 294 | feeRecipient ag_solanago.PublicKey, 295 | initialVirtualTokenReserves uint64, 296 | initialVirtualSolReserves uint64, 297 | initialRealTokenReserves uint64, 298 | tokenTotalSupply uint64, 299 | feeBasisPoints uint64, 300 | // Accounts: 301 | global ag_solanago.PublicKey, 302 | user ag_solanago.PublicKey, 303 | systemProgram ag_solanago.PublicKey, 304 | eventAuthority ag_solanago.PublicKey, 305 | program ag_solanago.PublicKey) *SetParams { 306 | return NewSetParamsInstructionBuilder(). 307 | SetFeeRecipient(feeRecipient). 308 | SetInitialVirtualTokenReserves(initialVirtualTokenReserves). 309 | SetInitialVirtualSolReserves(initialVirtualSolReserves). 310 | SetInitialRealTokenReserves(initialRealTokenReserves). 311 | SetTokenTotalSupply(tokenTotalSupply). 312 | SetFeeBasisPoints(feeBasisPoints). 313 | SetGlobalAccount(global). 314 | SetUserAccount(user). 315 | SetSystemProgramAccount(systemProgram). 316 | SetEventAuthorityAccount(eventAuthority). 317 | SetProgramAccount(program) 318 | } 319 | -------------------------------------------------------------------------------- /pump/SetParams_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_SetParams(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("SetParams"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(SetParams) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(SetParams) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pump/Withdraw.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "errors" 7 | ag_binary "github.com/gagliardetto/binary" 8 | ag_solanago "github.com/gagliardetto/solana-go" 9 | ag_format "github.com/gagliardetto/solana-go/text/format" 10 | ag_treeout "github.com/gagliardetto/treeout" 11 | ) 12 | 13 | // Allows the admin to withdraw liquidity for a migration once the bonding curve completes 14 | type Withdraw struct { 15 | 16 | // [0] = [] global 17 | // 18 | // [1] = [] mint 19 | // 20 | // [2] = [WRITE] bondingCurve 21 | // 22 | // [3] = [WRITE] associatedBondingCurve 23 | // 24 | // [4] = [WRITE] associatedUser 25 | // 26 | // [5] = [WRITE, SIGNER] user 27 | // 28 | // [6] = [] systemProgram 29 | // 30 | // [7] = [] tokenProgram 31 | // 32 | // [8] = [] rent 33 | // 34 | // [9] = [] eventAuthority 35 | // 36 | // [10] = [] program 37 | ag_solanago.AccountMetaSlice `bin:"-"` 38 | } 39 | 40 | // NewWithdrawInstructionBuilder creates a new `Withdraw` instruction builder. 41 | func NewWithdrawInstructionBuilder() *Withdraw { 42 | nd := &Withdraw{ 43 | AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 11), 44 | } 45 | return nd 46 | } 47 | 48 | // SetGlobalAccount sets the "global" account. 49 | func (inst *Withdraw) SetGlobalAccount(global ag_solanago.PublicKey) *Withdraw { 50 | inst.AccountMetaSlice[0] = ag_solanago.Meta(global) 51 | return inst 52 | } 53 | 54 | // GetGlobalAccount gets the "global" account. 55 | func (inst *Withdraw) GetGlobalAccount() *ag_solanago.AccountMeta { 56 | return inst.AccountMetaSlice.Get(0) 57 | } 58 | 59 | // SetMintAccount sets the "mint" account. 60 | func (inst *Withdraw) SetMintAccount(mint ag_solanago.PublicKey) *Withdraw { 61 | inst.AccountMetaSlice[1] = ag_solanago.Meta(mint) 62 | return inst 63 | } 64 | 65 | // GetMintAccount gets the "mint" account. 66 | func (inst *Withdraw) GetMintAccount() *ag_solanago.AccountMeta { 67 | return inst.AccountMetaSlice.Get(1) 68 | } 69 | 70 | // SetBondingCurveAccount sets the "bondingCurve" account. 71 | func (inst *Withdraw) SetBondingCurveAccount(bondingCurve ag_solanago.PublicKey) *Withdraw { 72 | inst.AccountMetaSlice[2] = ag_solanago.Meta(bondingCurve).WRITE() 73 | return inst 74 | } 75 | 76 | // GetBondingCurveAccount gets the "bondingCurve" account. 77 | func (inst *Withdraw) GetBondingCurveAccount() *ag_solanago.AccountMeta { 78 | return inst.AccountMetaSlice.Get(2) 79 | } 80 | 81 | // SetAssociatedBondingCurveAccount sets the "associatedBondingCurve" account. 82 | func (inst *Withdraw) SetAssociatedBondingCurveAccount(associatedBondingCurve ag_solanago.PublicKey) *Withdraw { 83 | inst.AccountMetaSlice[3] = ag_solanago.Meta(associatedBondingCurve).WRITE() 84 | return inst 85 | } 86 | 87 | // GetAssociatedBondingCurveAccount gets the "associatedBondingCurve" account. 88 | func (inst *Withdraw) GetAssociatedBondingCurveAccount() *ag_solanago.AccountMeta { 89 | return inst.AccountMetaSlice.Get(3) 90 | } 91 | 92 | // SetAssociatedUserAccount sets the "associatedUser" account. 93 | func (inst *Withdraw) SetAssociatedUserAccount(associatedUser ag_solanago.PublicKey) *Withdraw { 94 | inst.AccountMetaSlice[4] = ag_solanago.Meta(associatedUser).WRITE() 95 | return inst 96 | } 97 | 98 | // GetAssociatedUserAccount gets the "associatedUser" account. 99 | func (inst *Withdraw) GetAssociatedUserAccount() *ag_solanago.AccountMeta { 100 | return inst.AccountMetaSlice.Get(4) 101 | } 102 | 103 | // SetUserAccount sets the "user" account. 104 | func (inst *Withdraw) SetUserAccount(user ag_solanago.PublicKey) *Withdraw { 105 | inst.AccountMetaSlice[5] = ag_solanago.Meta(user).WRITE().SIGNER() 106 | return inst 107 | } 108 | 109 | // GetUserAccount gets the "user" account. 110 | func (inst *Withdraw) GetUserAccount() *ag_solanago.AccountMeta { 111 | return inst.AccountMetaSlice.Get(5) 112 | } 113 | 114 | // SetSystemProgramAccount sets the "systemProgram" account. 115 | func (inst *Withdraw) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Withdraw { 116 | inst.AccountMetaSlice[6] = ag_solanago.Meta(systemProgram) 117 | return inst 118 | } 119 | 120 | // GetSystemProgramAccount gets the "systemProgram" account. 121 | func (inst *Withdraw) GetSystemProgramAccount() *ag_solanago.AccountMeta { 122 | return inst.AccountMetaSlice.Get(6) 123 | } 124 | 125 | // SetTokenProgramAccount sets the "tokenProgram" account. 126 | func (inst *Withdraw) SetTokenProgramAccount(tokenProgram ag_solanago.PublicKey) *Withdraw { 127 | inst.AccountMetaSlice[7] = ag_solanago.Meta(tokenProgram) 128 | return inst 129 | } 130 | 131 | // GetTokenProgramAccount gets the "tokenProgram" account. 132 | func (inst *Withdraw) GetTokenProgramAccount() *ag_solanago.AccountMeta { 133 | return inst.AccountMetaSlice.Get(7) 134 | } 135 | 136 | // SetRentAccount sets the "rent" account. 137 | func (inst *Withdraw) SetRentAccount(rent ag_solanago.PublicKey) *Withdraw { 138 | inst.AccountMetaSlice[8] = ag_solanago.Meta(rent) 139 | return inst 140 | } 141 | 142 | // GetRentAccount gets the "rent" account. 143 | func (inst *Withdraw) GetRentAccount() *ag_solanago.AccountMeta { 144 | return inst.AccountMetaSlice.Get(8) 145 | } 146 | 147 | // SetEventAuthorityAccount sets the "eventAuthority" account. 148 | func (inst *Withdraw) SetEventAuthorityAccount(eventAuthority ag_solanago.PublicKey) *Withdraw { 149 | inst.AccountMetaSlice[9] = ag_solanago.Meta(eventAuthority) 150 | return inst 151 | } 152 | 153 | // GetEventAuthorityAccount gets the "eventAuthority" account. 154 | func (inst *Withdraw) GetEventAuthorityAccount() *ag_solanago.AccountMeta { 155 | return inst.AccountMetaSlice.Get(9) 156 | } 157 | 158 | // SetProgramAccount sets the "program" account. 159 | func (inst *Withdraw) SetProgramAccount(program ag_solanago.PublicKey) *Withdraw { 160 | inst.AccountMetaSlice[10] = ag_solanago.Meta(program) 161 | return inst 162 | } 163 | 164 | // GetProgramAccount gets the "program" account. 165 | func (inst *Withdraw) GetProgramAccount() *ag_solanago.AccountMeta { 166 | return inst.AccountMetaSlice.Get(10) 167 | } 168 | 169 | func (inst Withdraw) Build() *Instruction { 170 | return &Instruction{BaseVariant: ag_binary.BaseVariant{ 171 | Impl: inst, 172 | TypeID: Instruction_Withdraw, 173 | }} 174 | } 175 | 176 | // ValidateAndBuild validates the instruction parameters and accounts; 177 | // if there is a validation error, it returns the error. 178 | // Otherwise, it builds and returns the instruction. 179 | func (inst Withdraw) ValidateAndBuild() (*Instruction, error) { 180 | if err := inst.Validate(); err != nil { 181 | return nil, err 182 | } 183 | return inst.Build(), nil 184 | } 185 | 186 | func (inst *Withdraw) Validate() error { 187 | // Check whether all (required) accounts are set: 188 | { 189 | if inst.AccountMetaSlice[0] == nil { 190 | return errors.New("accounts.Global is not set") 191 | } 192 | if inst.AccountMetaSlice[1] == nil { 193 | return errors.New("accounts.Mint is not set") 194 | } 195 | if inst.AccountMetaSlice[2] == nil { 196 | return errors.New("accounts.BondingCurve is not set") 197 | } 198 | if inst.AccountMetaSlice[3] == nil { 199 | return errors.New("accounts.AssociatedBondingCurve is not set") 200 | } 201 | if inst.AccountMetaSlice[4] == nil { 202 | return errors.New("accounts.AssociatedUser is not set") 203 | } 204 | if inst.AccountMetaSlice[5] == nil { 205 | return errors.New("accounts.User is not set") 206 | } 207 | if inst.AccountMetaSlice[6] == nil { 208 | return errors.New("accounts.SystemProgram is not set") 209 | } 210 | if inst.AccountMetaSlice[7] == nil { 211 | return errors.New("accounts.TokenProgram is not set") 212 | } 213 | if inst.AccountMetaSlice[8] == nil { 214 | return errors.New("accounts.Rent is not set") 215 | } 216 | if inst.AccountMetaSlice[9] == nil { 217 | return errors.New("accounts.EventAuthority is not set") 218 | } 219 | if inst.AccountMetaSlice[10] == nil { 220 | return errors.New("accounts.Program is not set") 221 | } 222 | } 223 | return nil 224 | } 225 | 226 | func (inst *Withdraw) EncodeToTree(parent ag_treeout.Branches) { 227 | parent.Child(ag_format.Program(ProgramName, ProgramID)). 228 | // 229 | ParentFunc(func(programBranch ag_treeout.Branches) { 230 | programBranch.Child(ag_format.Instruction("Withdraw")). 231 | // 232 | ParentFunc(func(instructionBranch ag_treeout.Branches) { 233 | 234 | // Parameters of the instruction: 235 | instructionBranch.Child("Params[len=0]").ParentFunc(func(paramsBranch ag_treeout.Branches) {}) 236 | 237 | // Accounts of the instruction: 238 | instructionBranch.Child("Accounts[len=11]").ParentFunc(func(accountsBranch ag_treeout.Branches) { 239 | accountsBranch.Child(ag_format.Meta(" global", inst.AccountMetaSlice.Get(0))) 240 | accountsBranch.Child(ag_format.Meta(" mint", inst.AccountMetaSlice.Get(1))) 241 | accountsBranch.Child(ag_format.Meta(" bondingCurve", inst.AccountMetaSlice.Get(2))) 242 | accountsBranch.Child(ag_format.Meta("associatedBondingCurve", inst.AccountMetaSlice.Get(3))) 243 | accountsBranch.Child(ag_format.Meta(" associatedUser", inst.AccountMetaSlice.Get(4))) 244 | accountsBranch.Child(ag_format.Meta(" user", inst.AccountMetaSlice.Get(5))) 245 | accountsBranch.Child(ag_format.Meta(" systemProgram", inst.AccountMetaSlice.Get(6))) 246 | accountsBranch.Child(ag_format.Meta(" tokenProgram", inst.AccountMetaSlice.Get(7))) 247 | accountsBranch.Child(ag_format.Meta(" rent", inst.AccountMetaSlice.Get(8))) 248 | accountsBranch.Child(ag_format.Meta(" eventAuthority", inst.AccountMetaSlice.Get(9))) 249 | accountsBranch.Child(ag_format.Meta(" program", inst.AccountMetaSlice.Get(10))) 250 | }) 251 | }) 252 | }) 253 | } 254 | 255 | func (obj Withdraw) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 256 | return nil 257 | } 258 | func (obj *Withdraw) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 259 | return nil 260 | } 261 | 262 | // NewWithdrawInstruction declares a new Withdraw instruction with the provided parameters and accounts. 263 | func NewWithdrawInstruction( 264 | // Accounts: 265 | global ag_solanago.PublicKey, 266 | mint ag_solanago.PublicKey, 267 | bondingCurve ag_solanago.PublicKey, 268 | associatedBondingCurve ag_solanago.PublicKey, 269 | associatedUser ag_solanago.PublicKey, 270 | user ag_solanago.PublicKey, 271 | systemProgram ag_solanago.PublicKey, 272 | tokenProgram ag_solanago.PublicKey, 273 | rent ag_solanago.PublicKey, 274 | eventAuthority ag_solanago.PublicKey, 275 | program ag_solanago.PublicKey) *Withdraw { 276 | return NewWithdrawInstructionBuilder(). 277 | SetGlobalAccount(global). 278 | SetMintAccount(mint). 279 | SetBondingCurveAccount(bondingCurve). 280 | SetAssociatedBondingCurveAccount(associatedBondingCurve). 281 | SetAssociatedUserAccount(associatedUser). 282 | SetUserAccount(user). 283 | SetSystemProgramAccount(systemProgram). 284 | SetTokenProgramAccount(tokenProgram). 285 | SetRentAccount(rent). 286 | SetEventAuthorityAccount(eventAuthority). 287 | SetProgramAccount(program) 288 | } 289 | -------------------------------------------------------------------------------- /pump/Withdraw_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | ag_gofuzz "github.com/gagliardetto/gofuzz" 8 | ag_require "github.com/stretchr/testify/require" 9 | "strconv" 10 | "testing" 11 | ) 12 | 13 | func TestEncodeDecode_Withdraw(t *testing.T) { 14 | fu := ag_gofuzz.New().NilChance(0) 15 | for i := 0; i < 1; i++ { 16 | t.Run("Withdraw"+strconv.Itoa(i), func(t *testing.T) { 17 | { 18 | params := new(Withdraw) 19 | fu.Fuzz(params) 20 | params.AccountMetaSlice = nil 21 | buf := new(bytes.Buffer) 22 | err := encodeT(*params, buf) 23 | ag_require.NoError(t, err) 24 | got := new(Withdraw) 25 | err = decodeT(got, buf.Bytes()) 26 | got.AccountMetaSlice = nil 27 | ag_require.NoError(t, err) 28 | ag_require.Equal(t, params, got) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pump/accounts.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "fmt" 7 | ag_binary "github.com/gagliardetto/binary" 8 | ag_solanago "github.com/gagliardetto/solana-go" 9 | ) 10 | 11 | type Global struct { 12 | Initialized bool 13 | Authority ag_solanago.PublicKey 14 | FeeRecipient ag_solanago.PublicKey 15 | InitialVirtualTokenReserves uint64 16 | InitialVirtualSolReserves uint64 17 | InitialRealTokenReserves uint64 18 | TokenTotalSupply uint64 19 | FeeBasisPoints uint64 20 | } 21 | 22 | var GlobalDiscriminator = [8]byte{167, 232, 232, 177, 200, 108, 114, 127} 23 | 24 | func (obj Global) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 25 | // Write account discriminator: 26 | err = encoder.WriteBytes(GlobalDiscriminator[:], false) 27 | if err != nil { 28 | return err 29 | } 30 | // Serialize `Initialized` param: 31 | err = encoder.Encode(obj.Initialized) 32 | if err != nil { 33 | return err 34 | } 35 | // Serialize `Authority` param: 36 | err = encoder.Encode(obj.Authority) 37 | if err != nil { 38 | return err 39 | } 40 | // Serialize `FeeRecipient` param: 41 | err = encoder.Encode(obj.FeeRecipient) 42 | if err != nil { 43 | return err 44 | } 45 | // Serialize `InitialVirtualTokenReserves` param: 46 | err = encoder.Encode(obj.InitialVirtualTokenReserves) 47 | if err != nil { 48 | return err 49 | } 50 | // Serialize `InitialVirtualSolReserves` param: 51 | err = encoder.Encode(obj.InitialVirtualSolReserves) 52 | if err != nil { 53 | return err 54 | } 55 | // Serialize `InitialRealTokenReserves` param: 56 | err = encoder.Encode(obj.InitialRealTokenReserves) 57 | if err != nil { 58 | return err 59 | } 60 | // Serialize `TokenTotalSupply` param: 61 | err = encoder.Encode(obj.TokenTotalSupply) 62 | if err != nil { 63 | return err 64 | } 65 | // Serialize `FeeBasisPoints` param: 66 | err = encoder.Encode(obj.FeeBasisPoints) 67 | if err != nil { 68 | return err 69 | } 70 | return nil 71 | } 72 | 73 | func (obj *Global) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 74 | // Read and check account discriminator: 75 | { 76 | discriminator, err := decoder.ReadTypeID() 77 | if err != nil { 78 | return err 79 | } 80 | if !discriminator.Equal(GlobalDiscriminator[:]) { 81 | return fmt.Errorf( 82 | "wrong discriminator: wanted %s, got %s", 83 | "[167 232 232 177 200 108 114 127]", 84 | fmt.Sprint(discriminator[:])) 85 | } 86 | } 87 | // Deserialize `Initialized`: 88 | err = decoder.Decode(&obj.Initialized) 89 | if err != nil { 90 | return err 91 | } 92 | // Deserialize `Authority`: 93 | err = decoder.Decode(&obj.Authority) 94 | if err != nil { 95 | return err 96 | } 97 | // Deserialize `FeeRecipient`: 98 | err = decoder.Decode(&obj.FeeRecipient) 99 | if err != nil { 100 | return err 101 | } 102 | // Deserialize `InitialVirtualTokenReserves`: 103 | err = decoder.Decode(&obj.InitialVirtualTokenReserves) 104 | if err != nil { 105 | return err 106 | } 107 | // Deserialize `InitialVirtualSolReserves`: 108 | err = decoder.Decode(&obj.InitialVirtualSolReserves) 109 | if err != nil { 110 | return err 111 | } 112 | // Deserialize `InitialRealTokenReserves`: 113 | err = decoder.Decode(&obj.InitialRealTokenReserves) 114 | if err != nil { 115 | return err 116 | } 117 | // Deserialize `TokenTotalSupply`: 118 | err = decoder.Decode(&obj.TokenTotalSupply) 119 | if err != nil { 120 | return err 121 | } 122 | // Deserialize `FeeBasisPoints`: 123 | err = decoder.Decode(&obj.FeeBasisPoints) 124 | if err != nil { 125 | return err 126 | } 127 | return nil 128 | } 129 | 130 | type BondingCurve struct { 131 | VirtualTokenReserves uint64 132 | VirtualSolReserves uint64 133 | RealTokenReserves uint64 134 | RealSolReserves uint64 135 | TokenTotalSupply uint64 136 | Complete bool 137 | } 138 | 139 | var BondingCurveDiscriminator = [8]byte{23, 183, 248, 55, 96, 216, 172, 96} 140 | 141 | func (obj BondingCurve) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { 142 | // Write account discriminator: 143 | err = encoder.WriteBytes(BondingCurveDiscriminator[:], false) 144 | if err != nil { 145 | return err 146 | } 147 | // Serialize `VirtualTokenReserves` param: 148 | err = encoder.Encode(obj.VirtualTokenReserves) 149 | if err != nil { 150 | return err 151 | } 152 | // Serialize `VirtualSolReserves` param: 153 | err = encoder.Encode(obj.VirtualSolReserves) 154 | if err != nil { 155 | return err 156 | } 157 | // Serialize `RealTokenReserves` param: 158 | err = encoder.Encode(obj.RealTokenReserves) 159 | if err != nil { 160 | return err 161 | } 162 | // Serialize `RealSolReserves` param: 163 | err = encoder.Encode(obj.RealSolReserves) 164 | if err != nil { 165 | return err 166 | } 167 | // Serialize `TokenTotalSupply` param: 168 | err = encoder.Encode(obj.TokenTotalSupply) 169 | if err != nil { 170 | return err 171 | } 172 | // Serialize `Complete` param: 173 | err = encoder.Encode(obj.Complete) 174 | if err != nil { 175 | return err 176 | } 177 | return nil 178 | } 179 | 180 | func (obj *BondingCurve) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { 181 | // Read and check account discriminator: 182 | { 183 | discriminator, err := decoder.ReadTypeID() 184 | if err != nil { 185 | return err 186 | } 187 | if !discriminator.Equal(BondingCurveDiscriminator[:]) { 188 | return fmt.Errorf( 189 | "wrong discriminator: wanted %s, got %s", 190 | "[23 183 248 55 96 216 172 96]", 191 | fmt.Sprint(discriminator[:])) 192 | } 193 | } 194 | // Deserialize `VirtualTokenReserves`: 195 | err = decoder.Decode(&obj.VirtualTokenReserves) 196 | if err != nil { 197 | return err 198 | } 199 | // Deserialize `VirtualSolReserves`: 200 | err = decoder.Decode(&obj.VirtualSolReserves) 201 | if err != nil { 202 | return err 203 | } 204 | // Deserialize `RealTokenReserves`: 205 | err = decoder.Decode(&obj.RealTokenReserves) 206 | if err != nil { 207 | return err 208 | } 209 | // Deserialize `RealSolReserves`: 210 | err = decoder.Decode(&obj.RealSolReserves) 211 | if err != nil { 212 | return err 213 | } 214 | // Deserialize `TokenTotalSupply`: 215 | err = decoder.Decode(&obj.TokenTotalSupply) 216 | if err != nil { 217 | return err 218 | } 219 | // Deserialize `Complete`: 220 | err = decoder.Decode(&obj.Complete) 221 | if err != nil { 222 | return err 223 | } 224 | return nil 225 | } 226 | -------------------------------------------------------------------------------- /pump/instructions.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | ag_spew "github.com/davecgh/go-spew/spew" 9 | ag_binary "github.com/gagliardetto/binary" 10 | ag_solanago "github.com/gagliardetto/solana-go" 11 | ag_text "github.com/gagliardetto/solana-go/text" 12 | ag_treeout "github.com/gagliardetto/treeout" 13 | ) 14 | 15 | var ProgramID ag_solanago.PublicKey = ag_solanago.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") 16 | 17 | func SetProgramID(pubkey ag_solanago.PublicKey) { 18 | ProgramID = pubkey 19 | ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) 20 | } 21 | 22 | const ProgramName = "Pump" 23 | 24 | func init() { 25 | if !ProgramID.IsZero() { 26 | ag_solanago.RegisterInstructionDecoder(ProgramID, registryDecodeInstruction) 27 | } 28 | } 29 | 30 | var ( 31 | // Creates the global state. 32 | Instruction_Initialize = ag_binary.TypeID([8]byte{175, 175, 109, 31, 13, 152, 155, 237}) 33 | 34 | // Sets the global state parameters. 35 | Instruction_SetParams = ag_binary.TypeID([8]byte{27, 234, 178, 52, 147, 2, 187, 141}) 36 | 37 | // Creates a new coin and bonding curve. 38 | Instruction_Create = ag_binary.TypeID([8]byte{24, 30, 200, 40, 5, 28, 7, 119}) 39 | 40 | // Buys tokens from a bonding curve. 41 | Instruction_Buy = ag_binary.TypeID([8]byte{102, 6, 61, 18, 1, 218, 235, 234}) 42 | 43 | // Sells tokens into a bonding curve. 44 | Instruction_Sell = ag_binary.TypeID([8]byte{51, 230, 133, 164, 1, 127, 131, 173}) 45 | 46 | // Allows the admin to withdraw liquidity for a migration once the bonding curve completes 47 | Instruction_Withdraw = ag_binary.TypeID([8]byte{183, 18, 70, 156, 148, 109, 161, 34}) 48 | ) 49 | 50 | // InstructionIDToName returns the name of the instruction given its ID. 51 | func InstructionIDToName(id ag_binary.TypeID) string { 52 | switch id { 53 | case Instruction_Initialize: 54 | return "Initialize" 55 | case Instruction_SetParams: 56 | return "SetParams" 57 | case Instruction_Create: 58 | return "Create" 59 | case Instruction_Buy: 60 | return "Buy" 61 | case Instruction_Sell: 62 | return "Sell" 63 | case Instruction_Withdraw: 64 | return "Withdraw" 65 | default: 66 | return "" 67 | } 68 | } 69 | 70 | type Instruction struct { 71 | ag_binary.BaseVariant 72 | } 73 | 74 | func (inst *Instruction) EncodeToTree(parent ag_treeout.Branches) { 75 | if enToTree, ok := inst.Impl.(ag_text.EncodableToTree); ok { 76 | enToTree.EncodeToTree(parent) 77 | } else { 78 | parent.Child(ag_spew.Sdump(inst)) 79 | } 80 | } 81 | 82 | var InstructionImplDef = ag_binary.NewVariantDefinition( 83 | ag_binary.AnchorTypeIDEncoding, 84 | []ag_binary.VariantType{ 85 | { 86 | "initialize", (*Initialize)(nil), 87 | }, 88 | { 89 | "set_params", (*SetParams)(nil), 90 | }, 91 | { 92 | "create", (*Create)(nil), 93 | }, 94 | { 95 | "buy", (*Buy)(nil), 96 | }, 97 | { 98 | "sell", (*Sell)(nil), 99 | }, 100 | { 101 | "withdraw", (*Withdraw)(nil), 102 | }, 103 | }, 104 | ) 105 | 106 | func (inst *Instruction) ProgramID() ag_solanago.PublicKey { 107 | return ProgramID 108 | } 109 | 110 | func (inst *Instruction) Accounts() (out []*ag_solanago.AccountMeta) { 111 | return inst.Impl.(ag_solanago.AccountsGettable).GetAccounts() 112 | } 113 | 114 | func (inst *Instruction) Data() ([]byte, error) { 115 | buf := new(bytes.Buffer) 116 | if err := ag_binary.NewBorshEncoder(buf).Encode(inst); err != nil { 117 | return nil, fmt.Errorf("unable to encode instruction: %w", err) 118 | } 119 | return buf.Bytes(), nil 120 | } 121 | 122 | func (inst *Instruction) TextEncode(encoder *ag_text.Encoder, option *ag_text.Option) error { 123 | return encoder.Encode(inst.Impl, option) 124 | } 125 | 126 | func (inst *Instruction) UnmarshalWithDecoder(decoder *ag_binary.Decoder) error { 127 | return inst.BaseVariant.UnmarshalBinaryVariant(decoder, InstructionImplDef) 128 | } 129 | 130 | func (inst *Instruction) MarshalWithEncoder(encoder *ag_binary.Encoder) error { 131 | err := encoder.WriteBytes(inst.TypeID.Bytes(), false) 132 | if err != nil { 133 | return fmt.Errorf("unable to write variant type: %w", err) 134 | } 135 | return encoder.Encode(inst.Impl) 136 | } 137 | 138 | func registryDecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (interface{}, error) { 139 | inst, err := DecodeInstruction(accounts, data) 140 | if err != nil { 141 | return nil, err 142 | } 143 | return inst, nil 144 | } 145 | 146 | func DecodeInstruction(accounts []*ag_solanago.AccountMeta, data []byte) (*Instruction, error) { 147 | inst := new(Instruction) 148 | if err := ag_binary.NewBorshDecoder(data).Decode(inst); err != nil { 149 | return nil, fmt.Errorf("unable to decode instruction: %w", err) 150 | } 151 | if v, ok := inst.Impl.(ag_solanago.AccountsSettable); ok { 152 | err := v.SetAccounts(accounts) 153 | if err != nil { 154 | return nil, fmt.Errorf("unable to set accounts for instruction: %w", err) 155 | } 156 | } 157 | return inst, nil 158 | } 159 | -------------------------------------------------------------------------------- /pump/testing_utils.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | ag_binary "github.com/gagliardetto/binary" 9 | ) 10 | 11 | func encodeT(data interface{}, buf *bytes.Buffer) error { 12 | if err := ag_binary.NewBorshEncoder(buf).Encode(data); err != nil { 13 | return fmt.Errorf("unable to encode instruction: %w", err) 14 | } 15 | return nil 16 | } 17 | 18 | func decodeT(dst interface{}, data []byte) error { 19 | return ag_binary.NewBorshDecoder(data).Decode(dst) 20 | } 21 | -------------------------------------------------------------------------------- /pump/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. 2 | 3 | package pump 4 | -------------------------------------------------------------------------------- /sell-coin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/1fge/pump-fun-sniper-bot/pump" 10 | "github.com/gagliardetto/solana-go" 11 | associatedtokenaccount "github.com/gagliardetto/solana-go/programs/associated-token-account" 12 | cb "github.com/gagliardetto/solana-go/programs/compute-budget" 13 | "github.com/gagliardetto/solana-go/programs/token" 14 | ) 15 | 16 | // SellCoinFast utilizes the fact that, unlike buying, we do not care if duplicate tx hit the chain 17 | // if they do, we lose the priority fee, but ensure we are out of the position quickly. For this reason, 18 | // we spam sell transactions every 400ms for a duration of 6 seconds, resulting in 15 sell tx 19 | func (b *Bot) SellCoinFast(coin *Coin) { 20 | fmt.Println("Preparing to sell coin", coin.mintAddr.String()) 21 | // send off sell requests separated by 400ms, wait for one to return 22 | // valid transaction, otherwise repeat (for 45 seconds at most) 23 | coin.isSellingCoin = true 24 | defer coin.setExitedSellCoinTrue() 25 | 26 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*6) 27 | defer cancel() 28 | 29 | ticker := time.NewTicker(400 * time.Millisecond) 30 | defer ticker.Stop() 31 | 32 | result := make(chan int, 1) // Buffered to ensure non-blocking send 33 | var sendVanilla = true 34 | 35 | // goroutine to send off sell tx every 400 until confirmed 36 | go func() { 37 | for { 38 | select { 39 | case <-ticker.C: 40 | // alternate between jito and vanilla each iteration, in case of no jito leader 41 | sendVanilla = !sendVanilla 42 | go b.sellCoinWrapper(coin, result, sendVanilla) 43 | case <-ctx.Done(): 44 | return // Stop the ticker loop when context is cancelled 45 | } 46 | } 47 | }() 48 | 49 | // wait for first result to come back 50 | <-result 51 | time.Sleep(1 * time.Second) 52 | } 53 | 54 | func (b *Bot) sellCoinWrapper(coin *Coin, result chan int, sendVanilla bool) { 55 | sellSignature, err := b.sellCoin(coin, sendVanilla) 56 | if err != nil { 57 | if err != context.Canceled { 58 | if sellSignature != nil { 59 | b.statusr(fmt.Sprintf("Sell transaction %s failed: %s", sellSignature.String(), err)) 60 | } else { 61 | b.statusr(fmt.Sprintf("Sell transaction failed: %s", err)) 62 | } 63 | } 64 | 65 | return 66 | } 67 | 68 | if sellSignature == nil { 69 | fmt.Println("Sell signature is nil") 70 | return 71 | } 72 | 73 | result <- 1 74 | } 75 | 76 | func (b *Bot) sellCoin(coin *Coin, sendVanilla bool) (*solana.Signature, error) { 77 | if coin == nil { 78 | return nil, errNilCoin 79 | } 80 | 81 | sellInstruction := b.createSellInstruction(coin) 82 | culInst := cb.NewSetComputeUnitLimitInstruction(uint32(computeUnitLimits)) 83 | cupInst := cb.NewSetComputeUnitPriceInstruction(b.feeMicroLamport) 84 | instructions := []solana.Instruction{cupInst.Build(), culInst.Build(), sellInstruction.Build()} 85 | 86 | // enable jito if it's jito leader and we do not force vanilla tx 87 | enableJito := b.jitoManager.isJitoLeader() && !sendVanilla 88 | if enableJito { 89 | coin.status("Jito leader, setting tip & removing priority fee inst") 90 | tipInst, err := b.jitoManager.generateTipInstruction() 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | 95 | instructions = append(instructions, tipInst) 96 | 97 | // IMPORTANT: remove priority fee when we jito tip 98 | instructions = instructions[1:] 99 | } 100 | 101 | tx, err := b.createTransaction(instructions...) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | return b.signAndSendTx(tx, enableJito) 107 | } 108 | 109 | func (b *Bot) createSellInstruction(coin *Coin) *pump.Sell { 110 | // we want a minimum of 1 lamport, which ensures we should get filled at any price 111 | // as long as any of the 15 tx land 112 | minimumLamports := uint64(1) 113 | 114 | return pump.NewSellInstruction( 115 | coin.tokensHeld.Uint64(), 116 | minimumLamports, 117 | globalAddr, 118 | feeRecipient, 119 | coin.mintAddr, 120 | coin.tokenBondingCurve, 121 | coin.associatedBondingCurve, 122 | coin.associatedTokenAccount, 123 | b.privateKey.PublicKey(), 124 | solana.SystemProgramID, 125 | associatedtokenaccount.ProgramID, 126 | token.ProgramID, 127 | coin.eventAuthority, 128 | pumpProgramID, 129 | ) 130 | } 131 | 132 | func (c *Coin) setExitedSellCoinTrue() { 133 | c.exitedSellCoin = true 134 | } 135 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | "net/http" 11 | "net/url" 12 | "sync" 13 | "time" 14 | 15 | "github.com/gagliardetto/solana-go/rpc/jsonrpc" 16 | 17 | "github.com/gagliardetto/solana-go" 18 | "github.com/gagliardetto/solana-go/rpc" 19 | "github.com/gagliardetto/solana-go/rpc/ws" 20 | _ "github.com/go-sql-driver/mysql" 21 | ) 22 | 23 | var ( 24 | errDBConnectionNil = errors.New("MySQL DB Connection Nil") 25 | 26 | pumpProgramID solana.PublicKey = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") 27 | globalAddr solana.PublicKey = solana.MustPublicKeyFromBase58("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf") 28 | feeRecipient solana.PublicKey = solana.MustPublicKeyFromBase58("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") 29 | rent solana.PublicKey = solana.MustPublicKeyFromBase58("SysvarRent111111111111111111111111111111111") 30 | ) 31 | 32 | type Bot struct { 33 | rpcClient *rpc.Client 34 | jrpcClient rpc.JSONRPCClient 35 | sendTxClients []*rpc.Client 36 | 37 | wsClient *ws.Client 38 | privateKey solana.PrivateKey 39 | dbConnection *sql.DB 40 | 41 | feeMicroLamport uint64 42 | buyAmountLamport uint64 // amount of coins we buy for each coin (in lamports) 43 | 44 | pendingCoins map[string]*Coin // coins which we will attempt to buy, but have yet to be purchased 45 | pendingCoinsLock sync.Mutex 46 | coinsToBuy chan *Coin 47 | coinsToSell chan string 48 | 49 | // skipATALookup skips looking up if the ATA exists. Useful for debugging & attempting to purchase coins we already have owned. 50 | // in prod, should always be set to `true` since we should never have ATA for new coins. 51 | skipATALookup bool 52 | 53 | blockhash *solana.Hash 54 | jitoManager *JitoManager 55 | } 56 | 57 | func (b *Bot) status(msg interface{}) { 58 | log.Println("Bot", fmt.Sprintf("%v", msg)) 59 | } 60 | 61 | func (b *Bot) statusy(msg interface{}) { 62 | log.Println("Bot (Y)", fmt.Sprintf("%v", msg)) 63 | } 64 | 65 | func (b *Bot) statusg(msg interface{}) { 66 | log.Println("Bot (G)", fmt.Sprintf("%v", msg)) 67 | } 68 | 69 | func (b *Bot) statusr(msg interface{}) { 70 | log.Println("Bot (R)", fmt.Sprintf("%v", msg)) 71 | } 72 | 73 | type Coin struct { 74 | pickupTime time.Time // used to make sure duration / timings are good 75 | 76 | mintAddr solana.PublicKey 77 | tokenBondingCurve solana.PublicKey 78 | associatedBondingCurve solana.PublicKey 79 | eventAuthority solana.PublicKey 80 | 81 | creator solana.PublicKey 82 | creatorATA solana.PublicKey 83 | creatorPurchased bool 84 | creatorPurchaseSol float64 // actual solana amount of buy, not lamports 85 | 86 | // our values related to the coin once we buy / decide to buy, and afterwards 87 | creatorSold bool // has creator sold? 88 | botPurchased bool // separate bool. 89 | 90 | exitedBuyCoin bool // trigger to notify that we have finished all buy ops 91 | exitedSellCoin bool // trigger to notify that we have exited sell code routine 92 | exitedCreatorListener bool // trigger to notify that we stopped listening to creator sell 93 | 94 | isSellingCoin bool // lets program know that we are already in the process of selling coin to avoid dup sell 95 | 96 | associatedTokenAccount solana.PublicKey // our wallet's ata for this coin 97 | tokensHeld *big.Int 98 | 99 | buyPrice uint64 100 | buyTransactionSignature *solana.Signature 101 | } 102 | 103 | func (c *Coin) status(msg interface{}) { 104 | log.Println(c.mintAddr.String(), fmt.Sprintf("%v", msg)) 105 | } 106 | 107 | func proxiedClient(endpoint string) jsonrpc.RPCClient { 108 | u, _ := url.Parse(proxyURL) 109 | opts := &jsonrpc.RPCClientOpts{ 110 | HTTPClient: &http.Client{ 111 | Transport: &http.Transport{ 112 | Proxy: http.ProxyURL(u), 113 | }, 114 | }, 115 | } 116 | 117 | return jsonrpc.NewClientWithOpts(endpoint, opts) 118 | } 119 | 120 | // NewBot creates a new bot struct that we use to buy & sell coins 121 | func NewBot(rpcURL, wsURL, privateKey string, dbConnection *sql.DB, buySol float64, feeMicroLamport uint64) (*Bot, error) { 122 | var rpcClient *rpc.Client 123 | var jrpcClient rpc.JSONRPCClient 124 | 125 | if shouldProxy { 126 | rpcClient = rpc.NewWithCustomRPCClient(proxiedClient(rpcURL)) 127 | jrpcClient = proxiedClient(rpcURL) 128 | } else { 129 | rpcClient = rpc.New(rpcURL) 130 | jrpcClient = rpc.NewWithRateLimit(rpcURL, 500) 131 | } 132 | 133 | wsClient, err := ws.Connect(context.Background(), wsURL) 134 | if err != nil { 135 | fmt.Println("ws connection err", err) 136 | return nil, err 137 | } 138 | 139 | if dbConnection == nil { 140 | return nil, errDBConnectionNil 141 | } 142 | 143 | botPrivKey, err := solana.PrivateKeyFromBase58(privateKey) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | buySolToLamport := buySol * float64(solana.LAMPORTS_PER_SOL) 149 | 150 | jitoManager, err := newJitoManager(rpcClient, botPrivKey) 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | var sendTxClients []*rpc.Client 156 | for _, txRPC := range sendTxRPCs { 157 | sendTxClients = append(sendTxClients, rpc.New(txRPC)) 158 | } 159 | 160 | b := &Bot{ 161 | rpcClient: rpcClient, 162 | jrpcClient: jrpcClient, 163 | wsClient: wsClient, 164 | sendTxClients: sendTxClients, 165 | 166 | privateKey: botPrivKey, 167 | dbConnection: dbConnection, 168 | buyAmountLamport: uint64(buySolToLamport), 169 | feeMicroLamport: feeMicroLamport, 170 | 171 | jitoManager: jitoManager, 172 | 173 | pendingCoins: make(map[string]*Coin), 174 | pendingCoinsLock: sync.Mutex{}, 175 | coinsToBuy: make(chan *Coin), 176 | coinsToSell: make(chan string), 177 | } 178 | 179 | b.fetchBlockhashLoop() 180 | return b, nil 181 | } 182 | 183 | func (b *Bot) beginJito() error { 184 | if err := b.jitoManager.start(); err != nil { 185 | return err 186 | } 187 | 188 | return nil 189 | } 190 | -------------------------------------------------------------------------------- /tip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "sync" 10 | "time" 11 | 12 | jito_go "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go" 13 | "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/clients/searcher_client" 14 | util "github.com/1fge/pump-fun-sniper-bot/pkg/jito-go/pkg" 15 | "github.com/gagliardetto/solana-go" 16 | "github.com/gagliardetto/solana-go/rpc" 17 | ) 18 | 19 | type validatorAPIResponse struct { 20 | Validators []*jitoValidator `json:"validators"` 21 | } 22 | 23 | type jitoValidator struct { 24 | VoteAccount string `json:"vote_account"` 25 | RunningJito bool `json:"running_jito"` 26 | } 27 | 28 | // JitoManager acts as the struct where we store important information for interacting 29 | // with Jito. This includes keeping track of the leaders, determining tip amount based on percentile, 30 | // and building the tip instruction. 31 | type JitoManager struct { 32 | client *http.Client 33 | rpcClient *rpc.Client 34 | 35 | privateKey solana.PrivateKey 36 | 37 | slotIndex uint64 38 | epoch uint64 39 | 40 | // jitoValidators is a map of validator IDs that are running Jito. 41 | jitoValidators map[string]bool 42 | 43 | // slotLeader maps slot to validator ID. 44 | slotLeader map[uint64]string 45 | 46 | // voteAccounts maps nodeAccount to voteAccount 47 | voteAccounts map[string]string 48 | 49 | lock *sync.Mutex 50 | 51 | // tipInfo maps the latest tip information from Jito. 52 | tipInfo *util.TipStreamInfo 53 | jitoClient *searcher_client.Client 54 | } 55 | 56 | func newJitoManager(rpcClient *rpc.Client, privateKey solana.PrivateKey) (*JitoManager, error) { 57 | jitoClient, err := searcher_client.New( 58 | context.Background(), 59 | jito_go.NewYork.BlockEngineURL, 60 | rpcClient, 61 | rpcClient, 62 | privateKey, 63 | nil, 64 | ) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return &JitoManager{ 70 | client: &http.Client{}, 71 | rpcClient: rpcClient, 72 | jitoClient: jitoClient, 73 | 74 | jitoValidators: make(map[string]bool), 75 | slotLeader: make(map[uint64]string), 76 | voteAccounts: make(map[string]string), 77 | 78 | lock: &sync.Mutex{}, 79 | 80 | privateKey: privateKey, 81 | }, nil 82 | } 83 | 84 | func (j *JitoManager) status(msg string) { 85 | log.Println("Jito Manager", msg) 86 | } 87 | 88 | func (j *JitoManager) statusr(msg string) { 89 | log.Println("Jito Manager (R)", msg) 90 | } 91 | 92 | func (j *JitoManager) generateTipInstruction() (solana.Instruction, error) { 93 | tipAmount := j.generateTipAmount() 94 | j.status(fmt.Sprintf("Generating tip instruction for %.5f SOL", float64(tipAmount)/1e9)) 95 | return j.jitoClient.GenerateTipRandomAccountInstruction(tipAmount, j.privateKey.PublicKey()) 96 | } 97 | 98 | func (j *JitoManager) generateTipAmount() uint64 { 99 | if j.tipInfo == nil { 100 | return 2000000 101 | } 102 | 103 | return uint64(j.tipInfo.LandedTips75ThPercentile * 1e9) 104 | } 105 | 106 | func (j *JitoManager) manageTipStream() { 107 | go func() { 108 | for { 109 | if err := j.subscribeTipStream(); err != nil { 110 | j.statusr("Error reading tip stream: " + err.Error()) 111 | } 112 | } 113 | }() 114 | } 115 | 116 | func (j *JitoManager) subscribeTipStream() error { 117 | infoChan, errChan, err := util.SubscribeTipStream(context.TODO()) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | for { 123 | select { 124 | case info := <-infoChan: 125 | j.status(fmt.Sprintf("Received tip stream (75th percentile=%.3fSOL, 95th percentile=%.3fSOL, 99th percentile=%.3fSOL)", info.LandedTips75ThPercentile, info.LandedTips95ThPercentile, info.LandedTips99ThPercentile)) 126 | j.tipInfo = info 127 | case err = <-errChan: 128 | return err 129 | } 130 | } 131 | } 132 | 133 | func (j *JitoManager) start() error { 134 | if j.jitoClient == nil { 135 | return nil 136 | } 137 | 138 | j.manageTipStream() 139 | 140 | if err := j.fetchJitoValidators(); err != nil { 141 | return err 142 | } 143 | 144 | if err := j.fetchLeaderSchedule(); err != nil { 145 | return err 146 | } 147 | 148 | if err := j.fetchVoteAccounts(); err != nil { 149 | return err 150 | } 151 | 152 | if err := j.fetchEpochInfo(); err != nil { 153 | return err 154 | } 155 | 156 | go func() { 157 | for { 158 | if err := j.fetchEpochInfo(); err != nil { 159 | fmt.Println("Failed to fetch epoch info: ", err) 160 | } 161 | 162 | time.Sleep(10 * time.Millisecond) 163 | } 164 | }() 165 | 166 | go func() { 167 | for { 168 | if err := j.fetchLeaderSchedule(); err != nil { 169 | fmt.Println("Failed to fetch epoch info: ", err) 170 | } 171 | 172 | time.Sleep(10 * time.Minute) 173 | } 174 | }() 175 | 176 | go func() { 177 | for { 178 | if err := j.fetchJitoValidators(); err != nil { 179 | fmt.Println("Failed to fetch epoch info: ", err) 180 | } 181 | 182 | time.Sleep(10 * time.Minute) 183 | } 184 | }() 185 | 186 | go func() { 187 | for { 188 | if err := j.fetchVoteAccounts(); err != nil { 189 | fmt.Println("Failed to fetch epoch info: ", err) 190 | } 191 | 192 | time.Sleep(10 * time.Minute) 193 | } 194 | }() 195 | 196 | return nil 197 | } 198 | 199 | func (j *JitoManager) isJitoLeader() bool { 200 | j.lock.Lock() 201 | defer j.lock.Unlock() 202 | 203 | validator, ok := j.slotLeader[j.slotIndex] 204 | if !ok { 205 | return false 206 | } 207 | 208 | j.status("Checking if validator is a Jito leader: " + validator) 209 | isLeader := j.jitoValidators[j.voteAccounts[validator]] 210 | 211 | return isLeader 212 | } 213 | 214 | func (j *JitoManager) fetchLeaderSchedule() error { 215 | j.status("Fetching leader schedule") 216 | 217 | scheduleResult, err := j.rpcClient.GetLeaderSchedule(context.Background()) 218 | if err != nil { 219 | return err 220 | } 221 | 222 | j.buildLeaderSchedule(&scheduleResult) 223 | 224 | return nil 225 | } 226 | 227 | func (j *JitoManager) buildLeaderSchedule(scheduleResult *rpc.GetLeaderScheduleResult) { 228 | j.lock.Lock() 229 | defer j.lock.Unlock() 230 | 231 | j.slotLeader = make(map[uint64]string) 232 | for validator, slots := range *scheduleResult { 233 | for _, slot := range slots { 234 | j.slotLeader[slot] = validator.String() 235 | } 236 | } 237 | } 238 | 239 | func (j *JitoManager) fetchVoteAccounts() error { 240 | j.status("Fetching vote accounts") 241 | 242 | voteAccounts, err := j.rpcClient.GetVoteAccounts(context.Background(), nil) 243 | if err != nil { 244 | return err 245 | } 246 | 247 | j.buildVoteAccounts(voteAccounts.Current) 248 | 249 | return nil 250 | } 251 | 252 | func (j *JitoManager) buildVoteAccounts(voteAccounts []rpc.VoteAccountsResult) { 253 | j.lock.Lock() 254 | defer j.lock.Unlock() 255 | 256 | for _, account := range voteAccounts { 257 | j.voteAccounts[account.NodePubkey.String()] = account.VotePubkey.String() 258 | } 259 | } 260 | 261 | func (j *JitoManager) fetchEpochInfo() error { 262 | schedule, err := j.rpcClient.GetEpochInfo(context.Background(), rpc.CommitmentFinalized) 263 | if err != nil { 264 | return err 265 | } 266 | 267 | j.slotIndex = schedule.SlotIndex 268 | if j.epoch != schedule.Epoch { 269 | if err = j.fetchLeaderSchedule(); err != nil { 270 | return err 271 | } 272 | 273 | j.epoch = schedule.Epoch 274 | } 275 | 276 | return nil 277 | } 278 | 279 | // fetchJitoValidators fetches the list of validators from the Jito network. 280 | func (j *JitoManager) fetchJitoValidators() error { 281 | j.status("Fetching jito-enabled validators") 282 | 283 | req, err := http.NewRequest("GET", "https://kobe.mainnet.jito.network/api/v1/validators", nil) 284 | if err != nil { 285 | return err 286 | } 287 | 288 | req.Header.Set("accept", "application/json") 289 | 290 | resp, err := j.client.Do(req) 291 | if err != nil { 292 | return err 293 | } 294 | 295 | defer resp.Body.Close() 296 | 297 | if resp.StatusCode != 200 { 298 | return fmt.Errorf("failed to fetch validators: %s", resp.Status) 299 | } 300 | 301 | var validators validatorAPIResponse 302 | err = json.NewDecoder(resp.Body).Decode(&validators) 303 | if err != nil { 304 | return err 305 | } 306 | 307 | j.buildJitoValidators(validators.Validators) 308 | 309 | return nil 310 | } 311 | 312 | func (j *JitoManager) buildJitoValidators(validators []*jitoValidator) { 313 | j.lock.Lock() 314 | defer j.lock.Unlock() 315 | j.jitoValidators = make(map[string]bool) 316 | 317 | for i := range validators { 318 | if validators[i].RunningJito { 319 | j.jitoValidators[validators[i].VoteAccount] = true 320 | } 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "strings" 9 | "time" 10 | 11 | _ "net/http/pprof" 12 | 13 | "github.com/gagliardetto/solana-go" 14 | "github.com/gagliardetto/solana-go/rpc" 15 | "github.com/gagliardetto/solana-go/rpc/jsonrpc" 16 | ) 17 | 18 | // increase lookup time for funders with some common exchange addresses 19 | var exchangeAddresses = map[string]interface{}{ 20 | "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2": nil, 21 | "42brAgAVNzMBP7aaktPvAmBSPEkehnFQejiZc53EpJFd": nil, 22 | "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ": nil, 23 | "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS": nil, 24 | "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE": nil, 25 | "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9": nil, 26 | "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S": nil, 27 | "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD": nil, 28 | "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm": nil, 29 | } 30 | 31 | func isExchangeAddress(address string) bool { 32 | _, ok := exchangeAddresses[address] 33 | return ok 34 | } 35 | 36 | // signAndSendTx sends off a transaction and listens for completion 37 | // it allows optional context to trigger fellow goroutines to stop sending / listening 38 | // if one has already completed 39 | func (b *Bot) signAndSendTx(tx *solana.Transaction, enableJito bool) (*solana.Signature, error) { 40 | txSig, err := tx.Sign( 41 | func(key solana.PublicKey) *solana.PrivateKey { 42 | if b.privateKey.PublicKey().Equals(key) { 43 | return &b.privateKey 44 | } 45 | return nil 46 | }, 47 | ) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | startTs := time.Now() 53 | 54 | if enableJito { 55 | b.statusy("Sending transaction (Jito) " + txSig[0].String()) 56 | 57 | _, err = b.jitoManager.jitoClient.BroadcastBundle([]*solana.Transaction{tx}) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | if err = b.waitForTransactionComplete(txSig[0]); err != nil { 63 | return nil, err 64 | } 65 | 66 | latency := time.Since(startTs).Milliseconds() 67 | b.statusg(fmt.Sprintf("Sent transaction (Jito) %s with latency %d ms", txSig[0].String(), latency)) 68 | 69 | return &txSig[0], nil 70 | } 71 | 72 | return b.sendTxVanilla(tx) 73 | } 74 | 75 | func (b *Bot) sendTxVanilla(tx *solana.Transaction) (*solana.Signature, error) { 76 | var txSig = tx.Signatures[0] 77 | var retries uint 78 | b.statusy("Sending Vanilla TX to Dedicated & Free RPCs: " + txSig.String()) 79 | // send off tx with our dedicated rpc aka `b.rpcClient` 80 | go func() { 81 | if _, err := b.rpcClient.SendTransactionWithOpts( 82 | context.TODO(), 83 | tx, 84 | rpc.TransactionOpts{ 85 | SkipPreflight: true, 86 | MaxRetries: &retries, 87 | }, 88 | ); err != nil { 89 | fmt.Println("Error Sending Vanilla TX (Dedicated RPC)", err) 90 | } 91 | }() 92 | 93 | // use our free / alternate RPCs to send txs 94 | for _, rpcClient := range b.sendTxClients { 95 | go func(client *rpc.Client) { 96 | if err := b.sendOneVanillaTX(tx, client); err != nil { 97 | if strings.Contains(err.Error(), "429") { 98 | fmt.Println("Error Sending 1 Vanilla TX (Free RPC) (Ratelimited)") 99 | } else { 100 | fmt.Println("Error Sending 1 Vanilla TX (Free RPC)", err) 101 | } 102 | 103 | } 104 | }(rpcClient) 105 | } 106 | 107 | if err := b.waitForTransactionComplete(txSig); err != nil { 108 | return nil, err 109 | } 110 | 111 | return &txSig, nil 112 | } 113 | 114 | func (b *Bot) sendOneVanillaTX(tx *solana.Transaction, rpcClient *rpc.Client) error { 115 | var retries uint 116 | _, err := rpcClient.SendTransactionWithOpts( 117 | context.TODO(), 118 | tx, 119 | rpc.TransactionOpts{ 120 | SkipPreflight: true, 121 | MaxRetries: &retries, 122 | }, 123 | ) 124 | 125 | return err 126 | } 127 | 128 | func (b *Bot) fetchNLastTrans(numberSigs int, address string, optCtx ...context.Context) (jsonrpc.RPCResponses, error) { 129 | var ctx = context.TODO() 130 | if len(optCtx) > 0 { 131 | ctx = optCtx[0] 132 | } 133 | 134 | signatures, err := b.rpcClient.GetSignaturesForAddressWithOpts( 135 | ctx, 136 | solana.MustPublicKeyFromBase58(address), 137 | &rpc.GetSignaturesForAddressOpts{ 138 | Commitment: rpc.CommitmentConfirmed, 139 | Limit: &numberSigs, 140 | }, 141 | ) 142 | if err != nil { 143 | if strings.Contains(err.Error(), "context deadline") { 144 | fmt.Println("Context timeout for", address) 145 | return nil, errors.New("context timeout") 146 | } 147 | 148 | log.Printf("Failed to fetch transactions for %s: %v\n", address, err) 149 | return nil, err 150 | } 151 | 152 | requests := make([]*jsonrpc.RPCRequest, len(signatures)) // Initializing an empty slice of pointers to RPCRequest structs 153 | 154 | for i, sig := range signatures { 155 | requests[i] = &jsonrpc.RPCRequest{ 156 | JSONRPC: "2.0", 157 | ID: i + 1, 158 | Method: "getTransaction", 159 | Params: []interface{}{sig.Signature, map[string]interface{}{"commitment": rpc.CommitmentConfirmed, "maxSupportedTransactionVersion": 0}}, 160 | } 161 | } 162 | 163 | responses, err := b.jrpcClient.CallBatch(context.TODO(), requests) 164 | if err != nil { 165 | b.statusr(err) 166 | return nil, err 167 | } 168 | 169 | return responses, nil 170 | } 171 | 172 | // botHoldsTokens is a way for the bot to immediately check if we hold tokens 173 | // does not represent whether we've bought yet or not. 174 | func (c *Coin) botHoldsTokens() bool { 175 | if c.tokensHeld == nil { 176 | return false 177 | } 178 | 179 | heldTokensInt := c.tokensHeld.Int64() 180 | 181 | // TODO: do some checks to make sure no int overflow with this code 182 | // fmt.Println("Showing held tokens of", heldTokensInt) 183 | return heldTokensInt > 100 184 | } 185 | 186 | func (b *Bot) waitForTransactionComplete(sig solana.Signature) error { 187 | b.statusy("Waiting for transaction " + sig.String() + " to complete") 188 | 189 | signatureSubscription, err := b.wsClient.SignatureSubscribe(sig, rpc.CommitmentConfirmed) 190 | if err != nil { 191 | return err 192 | } 193 | 194 | defer signatureSubscription.Unsubscribe() 195 | 196 | result, err := signatureSubscription.RecvWithTimeout(time.Duration(120) * time.Second) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | if result.Value.Err != nil { 202 | return fmt.Errorf("Error in transaction: %v", result.Value.Err) 203 | } 204 | 205 | return nil 206 | } 207 | 208 | // lateToBuy compares the virtual sol reserves held in 209 | // bonding curve compared to how much user bought of the coin, 210 | // letting us know if we would be second buyer with current bonding curve 211 | func (c *Coin) lateToBuy(bcd *BondingCurveData) bool { 212 | reservesLamports, _ := bcd.VirtualSolReserves.Float64() 213 | reservesSol := reservesLamports / float64(solana.LAMPORTS_PER_SOL) 214 | reservesLessCreatorSol := reservesSol - c.creatorPurchaseSol 215 | 216 | // consider data stale if someone in with more than 0.1 217 | // NOTE: we deduct 30 solana since that's already in bonding curve, provided by pump.fun 218 | return reservesLessCreatorSol-30 > 0.1 219 | } 220 | --------------------------------------------------------------------------------