Open Source Trading #2: Boop Sniping Optimization Journey
Intro
Sniping in the Solana trenches is slowly starting to reach levels of optimization resembling the late Ethereum onchain days. To be fair, this was always bound to happen, but launchpads like Boop.fun and Believe App exacerbated the issue by feeding snipers the exact info they need to determine if they should snipe a token or not (like the Twitter account responsible for deploying). Regardless, competing on Solana is generally becoming tougher as sniping tooling like Bloom, Peppermint, Bananagun, etc. have become more commoditized.
However, in the midst of this, opportunity can still be found on the edges where competition is at least temporarily not as fierce. That was the case here. In this article, I’ll walk through my Boop.fun sniping journey—a few sleepless nights that began four days before the platform’s official launch and lasted until the opportunity dried up and competition made it not worth sticking around.
Code will be linked at the bottom (warning: similar to the first article, I wrote this fast, not pretty).
Initial Idea
Four days before Boop.fun launched, I saw dingaling post about a new Solana launchpad he was working on. Since he’s well known and founded Pancakeswap, I figured this was going to capture attention, so it was worth looking into.
Following some onchain digging, a friend (@ VauntedPlatypus on Twitter) and I found a mainnet program that looked Boop.fun related, which wound up being the official program.
After some more digging, we were able to find how the official BOOP token would be distributed (by allocation %), but more interesting was that the program’s IDL was already public on mainnet. This meant that the data needed to figure out how to buy and sell coins on their bonding curves (and figure out how the curves worked math-wise, that they graduated to Raydium pools, etc.) was right there, easily accessible and plainly readable.
So, rather than focusing on sniping BOOP (though in hindsight, I should have done this as well), I initially opted to focus on trying to snipe the first couple coins deployed on the platform.
However, closer to launch time it became apparent that any Twitter account that would receive a BOOP airdrop would have to connect their Twitter and deploy a coin in order to claim. Given that Twitter airdrop recipients included some very notable accounts, I realized there was much more opportunity here than simply sniping the first coin—it may also be possible to snipe coins deployed by specific well-known Twitter users.
With that goal in mind, I started implementing early in order to (a) be ready to snipe anyone notable who deployed in the opening minutes, and (b) be aggressive before competition inevitably ramped up and made things harder.
Planning
There were three steps to outline before I actually got to writing any code: figuring out what data was needed to build a buy transaction, picking an initial method for ingesting this data, and determining the best way to know which Twitter account (if any) was responsible for a deploy.
#1: Data needed to build a buy transaction
Looking at the Boop program IDL, we can see the data and accounts needed to buy a coin:
The data portion was easy enough. You just had to define an amount of SOL to spend and a minimum number of tokens to receive.
The accounts portion was a bit more complex. If you knew the “mint” (token address), you could derive the bonding curve, vaults, and recipient token account addresses. Plus, accounts like the buyer (yourself), wsol, and system/token/associated token program addresses are pre-known constants.
However, since things weren’t live yet, I didn’t actually know what “config” and “vault authority” would be set to. Thankfully, they’re mentioned in the “create token” and “deploy bonding curve” instructions, both of which are called when deploying a token:
So, I decided that I’d have to grab the config and vault authority accounts from the deploy transaction, and then use them in my buy transaction. Later, when Boop was live and settled on a specific config and vault authority, I could make them constants and avoid this extra dependency.
#2: Initial method for ingesting this data
Like I mentioned above, I realized I’d (at least to start) need to grab some details from coin deploy transactions like the config and vault authority. Also, even if/when I could set those to constants to avoid having to grab them, I would need to monitor deploys anyways in order to grab token addresses and create a feed of tokens I could analyze and potentially snipe. To do that, I needed to monitor every Boop deploy.
To start, I opted to simply monitor logs coming from the Boop program and look for transactions with “Instruction: DeployBondingCurve” or “Instruction: DeployBondingCurveFallback” logs. For any transaction where the Boop program emitted those logs, I could analyze them, ensure they were valid token deployments, and grab any info I needed. Simple enough for a v1 implementation.
#3: Determining which Twitter account deployed a coin
Boop wasn’t actually live at the time, so there were no examples of Twitter deploys to analyze. However, there was an API (with documentation if you were clever enough to find it) that would fetch info about a Boop token, including a twitter user ID for the deployer if it was a Twitter deploy. So I started off by planning to use this API to check who deployed a given token.
In practice, this was not reliable because of (a) latency (the API would not immediately index new tokens), but more importantly (b) the API was often down day 1, as it was probably being overwhelmed by requests.
It turned out that there was a better way—every Boop deploy transaction came with data that included a “URI” leading to some metadata. For instance, here is the metadata for boldleonidas’s coin:
Notice the “twitter_user_id”, which in this case pointed to boldleonidas’s account. This was hosted by Boop on S3, and was uploaded there before tokens were even created. As long as you ensured the metadata was posted to the official Boop S3 bucket (and not a random service—some people used alternative services to deploy metadata for things like fake Ansem tokens to scam buyers), you could simply fetch this metadata directly. I chose to use this method.
Implementation V1
Now it was time to implement. I already had a Python library for Solana trading tools that allowed me to easily import common functions, like fetching token supply or buying coins through venues like Jupiter or Pump.fun, into any existing code, so I figured given the time constraints I could just write a V1 in Python and migrate to Rust for the extra speed boost if needed later. (Side note, migrating my entire trading tools library to Rust is a future project I may write about here).
I’d start by adding a Boop buy function into my library. Once that was done, I could write the actual sniper logic and have it utilize the new buy function.
#1: Building Boop buy logic
Conforming to my library’s usual format, here was the entry to the “buy a boop coin” logic:
As you can see, there’s a fair number of parameters. I wanted to offload most of the extra work to the sniping logic, so I wrote “buy_coin_boop” to accept:
A keypair to use for the buy.
A Solana client (RPC connection), only used in specific cases.
An httpx client, only used in specific cases.
Buy parameters like the amount of SOL to spend and the % of the coin to try and buy (at minimum).
The transaction type (my library supports transaction submission methods like regular RPC, Jito, Bloxroute, Nozomi, simulate only, etc.) and amount to tip if I used a tx service that required one.
Optional latest blockhash/blockheight; in practice my sniper would have these cached to avoid fetching them via RPC at buy time.
Whether to execute or not.
Six extra addresses. These were things I’d be pulling out of token deploy transactions, deriving, or setting to constants later. Logic around fetching them, deriving them, or using constants could be handled in my sniper logic, so I simply forced them all to be passed here.
The “submit_and_monitor_async_tx” function at the bottom handled actually sending the transaction and is fairly standard, but supports multiple ways of sending transactions. The more relevant function to explore is “build_boop_buy_tx”, which does everything needed to build a Boop buy transaction.
First, it would make two necessary RPC calls: fetching the token’s decimals and total supply, and determining whether the trader wallet already had a token account for this token or not.
But in my final implementation, you can see that I ended up hardcoding the decimals and total supply, since Boop always used the same numbers, and I used “idempotent” for creating the trader’s token account. This cut off some latency by removing the need for RPC calls in this function, and using “idempotent” meant the transaction would succeed regardless of whether the token account previously existed or not.
Next, I did some math on the minimum number of tokens to try and buy (based on my intended % of supply), created a WSOL token account for the trader using “idempotent” again, and derived the WSOL vault token account. I could have also moved the “derive bonding curves” logic here, but instead I already pass them in as parameters.
Then, using the analysis from earlier about which accounts and data the buy instruction accepts, I built the buy instruction. The accounts simply match up with the IDL’s ordering, and the input data uses a discriminator (first 8 bytes of “sha256(global:buy_token)”) with little-endian encoded SOL in and minimum number of tokens out. Forgive my “max_sol_in” naming, it’s mislabeled since Boop actually takes a set # of SOL in for a minimum # of tokens out.
Finally, I built the transaction with all instructions required, added a fee if using a service that requires one (like Jito), and compiled the transaction. Now that all the actual logic for buying a boop coin was in place, I could move on to the sniper itself.
#2: Sniper logic
The first implementation of this sniper had a fair amount of issues that raised latency, namely some non-async calls blocking the main loop, and only streaming confirmed token deploy transactions. I didn’t save the original code as I was editing the file over time, so I’m going to present what I ended up with.
The overall goal of the sniper is to, as fast as we can, pick up new Boop tokens, parse them to see if we should buy, and land buy transactions onchain. To do this, we listen to four different streams:
Logs on the boop program with “confirmed” commitment
Logs on the boop program with “processed” commitment
Program data changes on the Metaplex token metadata program with “confirmed” commitment
Program data changes on the Metaplex token metadata program with “processed” commitment
Keep in mind this is not fully optimal. In the next section I will go over future improvements I would have considered had I decided to continue to compete for this opportunity with lower latency.
Anyways, the reason we listen to all four of those is that, depending on a few things, one may pick up a new token deploy faster than the others.
To begin, the bot initializes an httpx client (will be used for metadata API calls), spawns a task for each of those four stream listeners, and spawns another task that will fetch and cache a new blockhash/blockheight every 15 seconds + (if needed) ping specific transaction services to keep a TCP connection alive.
As you can see, there’s also a “BUYABLE_IDS” parameter. I won’t reveal which IDs I targeted, but this was a list of Twitter user IDs whose deployed tokens I considered snipe-worthy.
Now, these streams are just raw data that needs to be filtered. For the first two streams (Boop program logs), we filter by checking the logs to see if this is likely a token deploy:
For the latter two streams (Metaplex token metadata program data changes), we try to analyze the changed metadata info to see if it is related to a Boop coin creation:
What the code above does is look at the new metadata found from the stream, attempt to decode it into the format that Boop uses (ignore it if not the right format), and then analyze the URI to see if it’s using Boop’s official S3 bucket. If it does fit the format and uses Boop’s S3 bucket, we consider it a new Boop token deploy and derive some addresses needed for buying (we can do this all without the deploy transaction itself, since “config” and “vault authority” are set to constants as we mentioned we’d do earlier).
In all four cases, once a Boop token deploy is found, the next step is to check if it was deployed by a Twitter account in the buyable IDs list. The first two streams (Boop program logs) have to first fetch the full transaction via RPC call to grab the token address and other accounts, while the latter two streams already have that info (noted in the paragraph above). The rest of the logic is basically the same, so we’ll look at what comes next in the case of the latter two streams:
To keep this simple, basically all we’re doing here is fetching the token’s metadata from the URI (retrying multiple times if needed), checking for a deployer Twitter ID, and then if we find one, we check if it’s an ID we care to snipe. There’s also a custom BIG_BUYABLE_IDS list that holds a subset of the buyable Twitter IDs and would cause the sniper to snipe with a larger amount of SOL.
Once that code block is done, buy_mode will either be 0 (don’t snipe) or a non-zero number meaning snipe. There are some remnants of original code in here that was meant to snipe the first token(s) deployed, but the updated logic uses buy_mode 2 = regular snipe and buy_mode 3 = big snipe. We move on to this:
If we’re sniping, the Bullx URL is printed (to easily go check on the coin manually), and exec_buy is called, which (depending on the configuration) buys between 1 and 3 times on varying wallets using the buy_coin_boop function built above. That’s it!
Future Optimization Ideas
This logic worked well in the initial hours of the Boop launch, often grabbing large chunks of supply of major Twitter account coins. However, as time went by, competition increased and even landing block 2-4 was too slow. After about 2 days, I decided to move on to something else rather than optimize further, especially since launches had died off anyways. But had I wanted to stay and compete, there were three main sources of latency to think about cutting.
#1: Metadata API call
In all logic branches above, we have to query the metadata URI to check the deployer’s Twitter ID. The latency on this check could have been reduced by colocating with Boop’s metadata S3 bucket.
(Side-note: Boop’s metadata URIs used token addresses. If you could somehow predict what future token addresses would be—i.e. if Boop used a deterministic process for creating new token addresses—you could potentially pre-fetch the metadata for the next coins before the mint transaction even hit the chain. I don’t think this was possible here, as Boop probably just grinds new tokens ending in “boop” on their side non-deterministically, but tricks like this are worth thinking about if it actually provides edge.
But for this specific opportunity, if fully optimized, you could likely land block 1 even with a metadata fetch. If anyone figured out better ideas, I’d be interested to hear them.)
#2: Picking up new deploys
The more complex optimization needed would be around actually seeing deploy transactions faster. Some of the optimizations mentioned earlier (monitoring the metadata program for new Boop coins to avoid having to fetch the full transaction data, as well as using both processed and confirmed commitments) were starting points, but not nearly enough.
There are many ways to go here: Helius enhanced websockets, various dedicated node + gRPC providers, and other services that offer even faster transaction streams. You also can and should colocate with your service of choice. Some setups are more optimal than others, but I’ll leave that research up to you; the goal is simply to reduce the latency of picking up the transaction so ideally you can land block 0/1.
#3: Local code speed
Finally, I’d rewrite this code in something like Rust rather than using Python. I didn’t actually do any benchmarking on how much time this would cut down here, but in general if you’re competing for block 0/1 opportunities, you probably want to use a faster language as long as you know what you’re doing.
Conclusion
The code can be found here. (A bunch of disclaimers: I would not run this code anymore, I take no liability for any ways this code is used, I haven’t run it in weeks, there could be bugs, I made an effort to iterate fast not make it pretty, there’s various places where tips/fee prices are set high, etc. DM me on Twitter if you have any questions / thoughts).
As with the first article in this series, I hope the content here inspires some ideas of your own. There’s a shrinking set of opportunities on Solana right now as the number of participants dwindles and volumes remain relatively low, but there’s still small places where you can find easier edge if you’re willing to look. It’s not just luck—there’s a few familiar faces I see onchain on almost every new Solana opportunity now.
As for myself, I’m moving more of my efforts to other chains/venues lately, and will most likely focus the next article on one of those. If you have any suggestions for a future article, comments or insights about this one, or ideas to discuss related to trades or sniping opportunities, feel free to DM me on Twitter @ bh359.